import pandas as pd
import swisseph as swe
import numpy as np
import re
DATA_FILE = 'pantheon_with_birth_order.csv'
OUTPUT_FILE = 'pantheon_analysis_results.csv'
AYANAMSA_LAHIRI = swe.SIDM_LAHIRI
swe.set_sid_mode(AYANAMSA_LAHIRI)
def parse_date(date_str):
"""
Parses dates like '1946-06-14' or '0566-04-08 BC'.
Returns (year, month, day) or None.
"""
if pd.isna(date_str):
return None
date_str = str(date_str).strip()
is_bc = 'BC' in date_str
# Remove ' BC' or other artifacts
clean_str = date_str.replace(' BC', '').strip()
parts = clean_str.split('-')
if len(parts) != 3:
return None
try:
y, m, d = int(parts[0]), int(parts[1]), int(parts[2])
if is_bc:
# Convert to astronomical year
# Historical 1 BC = Astronomical 0
# Historical 566 BC = 1 - 566 = -565
y = 1 - y
return (y, m, d)
except:
return None
def get_positions(y, m, d):
try:
jd = swe.julday(y, m, d, 12.0) # Noon
# Tropical Positions
flags_trop = swe.FLG_SWIEPH # Default is Tropical
sun_trop = swe.calc_ut(jd, swe.SUN, flags_trop)[0][0]
# Sidereal Positions
flags_sid = swe.FLG_SWIEPH | swe.FLG_SIDEREAL
sun_sid = swe.calc_ut(jd, swe.SUN, flags_sid)[0][0]
jup_sid = swe.calc_ut(jd, swe.JUPITER, flags_sid)[0][0]
sat_sid = swe.calc_ut(jd, swe.SATURN, flags_sid)[0][0]
return sun_trop, sun_sid, jup_sid, sat_sid
except:
return None, None, None, None
def get_sign_name(lon):
signs = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
return signs[int(lon / 30) % 12]
def get_sign_element(lon):
# Signs: 0=Aries (Fire), 1=Taurus (Earth), 2=Gemini (Air), 3=Cancer (Water)...
sign_idx = int(lon / 30) % 12
elements = ['Fire', 'Earth', 'Air', 'Water'] # Aries, Taurus, Gemini, Cancer...
return elements[sign_idx % 4]
def get_aspect(p1, p2, orb=8):
# Calculate shortest distance
diff = abs(p1 - p2) % 360
dist = min(diff, 360 - diff)
if dist < orb: return 'Conjunction'
if abs(dist - 180) < orb: return 'Opposition'
if abs(dist - 120) < orb: return 'Trine'
if abs(dist - 90) < orb: return 'Square'
if abs(dist - 60) < orb: return 'Sextile'
return 'None'
def analyze():
print(f"Loading {DATA_FILE}...")
df = pd.read_csv(DATA_FILE)
results = []
print("Calculating planetary data...")
for idx, row in df.iterrows():
bdate = row['birthdate']
y, m, d = 0,0,0
parsed = parse_date(bdate)
if not parsed:
continue
y, m, d = parsed
sun_trop, sun_sid, jup_sid, sat_sid = get_positions(y, m, d)
if sun_trop is None:
continue
sign_trop = get_sign_name(sun_trop)
sign_sid = get_sign_name(sun_sid)
element_sid = get_sign_element(sun_sid)
# element_trop = get_sign_element(sun_trop) # Not strictly requested but good for debugging
sat_aspect = get_aspect(sun_sid, sat_sid)
jup_aspect = get_aspect(sun_sid, jup_sid)
# Categorize birth order
bo = row['birth_order']
total = row['total_siblings'] # Note: this comes from wikidata count, might be incomplete
cat = 'Middle'
if bo == 1:
if total == 1: cat = 'Only'
else: cat = 'First'
elif bo == total and total > 1:
cat = 'Youngest'
results.append({
'name': row['name'],
'birth_order': bo,
'category': cat,
'sun_sign_trop': sign_trop,
'sun_sign_sid': sign_sid,
'sun_element_sid': element_sid,
'sun_saturn': sat_aspect,
'sun_jupiter': jup_aspect
})
res_df = pd.DataFrame(results)
# Filter for main categories
main_cats = res_df[res_df['category'].isin(['First', 'Middle', 'Youngest'])]
print("\n--- Tropical Sun Sign Distribution (%) ---")
ct_trop = pd.crosstab(main_cats['category'], main_cats['sun_sign_trop'], normalize='index') * 100
print(ct_trop.round(1))
print("\n--- Vedic (Sidereal) Sun Sign Distribution (%) ---")
ct_sid = pd.crosstab(main_cats['category'], main_cats['sun_sign_sid'], normalize='index') * 100
print(ct_sid.round(1))
print("\n--- Vedic Sun Element Distribution by Birth Order Category ---")
ct = pd.crosstab(res_df['category'], res_df['sun_element_sid'], normalize='index') * 100
print(ct.round(1))
print("\n--- Sun-Saturn Aspects by Category (Vedic) ---")
# Soft: Trine, Sextile. Hard: Square, Opposition, Conjunction
def classify_aspect(asp):
if asp == 'None': return 'None'
if asp in ['Trine', 'Sextile']: return 'Soft'
return 'Hard'
res_df['sat_type'] = res_df['sun_saturn'].apply(classify_aspect)
# Re-filter to include new column
main_cats = res_df[res_df['category'].isin(['First', 'Middle', 'Youngest'])]
ct_sat = pd.crosstab(main_cats['category'], main_cats['sat_type'], normalize='index') * 100
print(ct_sat.round(1))
print("\n--- Sun-Jupiter Aspects by Category (Vedic) ---")
res_df['jup_type'] = res_df['sun_jupiter'].apply(classify_aspect)
# Re-filter to include new column
main_cats = res_df[res_df['category'].isin(['First', 'Middle', 'Youngest'])]
ct_jup = pd.crosstab(main_cats['category'], main_cats['jup_type'], normalize='index') * 100
print(ct_jup.round(1))
# Save
res_df.to_csv(OUTPUT_FILE, index=False)
print(f"\nDetailed results saved to {OUTPUT_FILE}")
if __name__ == "__main__":
analyze()