Lesson 18 · Real-World Data Analytics
Python Data Analytics #18: Company Fundamentals from Real Financial Statements
Video eighteen of the hundred-video real-world data analytics series. Analyzing real fundamentals for over three hundred real S&P 500 companies, valuation,…
- CourseReal-World Data Analytics
- Lesson18 of 26
- Video27 min
- FormatJupyter notebook · 30 code cells
- Data1 dataset
What you'll learn
Datasets used in this lesson
Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.
- sp500_fundamentals.csv62.7 KB
📓 Full notebook
Download .ipynbData Analytics 100, Video 18: Company Fundamentals from Real Financial Statements#
- Video eighteen of the hundred-video real-world data analytics series.
- Analyzing real fundamentals for over three hundred real S&P 500 companies, valuation, profitability, and dividends.
- Let's get into it.
Part 1: A Real Snapshot, Not a Price Chart#
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
companies = pd.read_csv('sp500_fundamentals.csv')
companies.shape
Part 2: Real Columns Available#
companies.columns.tolist()
companies.head(3)
Part 3: Real Missing Data Audit#
companies.isna().sum().sort_values(ascending=False)
round(companies['Dividend Yield'].isna().mean() * 100, 1)
Part 4: Real Clean Subset for Valuation Analysis#
valuation_cols = ['Symbol', 'Name', 'Sector', 'Price', 'Price/Earnings', 'Market Cap', 'EBITDA', 'Price/Sales', 'Price/Book']
clean = companies.dropna(subset=valuation_cols).copy()
clean.shape
Part 5: Real Market Cap Leaders#
top_market_cap = clean.nlargest(10, 'Market Cap')[['Symbol', 'Name', 'Market Cap']]
top_market_cap
Part 6: Real Price-to-Earnings Distribution#
clean['Price/Earnings'].describe().round(2)
Part 7: Visualizing Real P/E Distribution#
plt.figure(figsize=(10, 5))
plt.hist(clean['Price/Earnings'].clip(upper=100), bins=40, color='steelblue', edgecolor='white')
plt.xlabel('Real Price-to-Earnings Ratio')
plt.ylabel('Real Number of Companies')
plt.title('Real P/E Ratio Distribution, S&P 500')
plt.tight_layout()
plt.savefig('pe_distribution.png', dpi=120)
plt.close()
Part 8: Real Cheapest Stocks by P/E#
cheapest = clean[clean['Price/Earnings'] > 0].nsmallest(10, 'Price/Earnings')[['Symbol', 'Name', 'Sector', 'Price/Earnings']]
cheapest
Part 9: Real Sector-Level Average Valuation#
sector_pe = clean.groupby('Sector')['Price/Earnings'].mean().sort_values(ascending=False)
sector_pe.head(10).round(2)
Part 10: Real Sector Company Counts#
sector_counts = clean['Sector'].value_counts()
sector_counts.head(10)
Part 11: Real Dividend-Paying Companies#
dividend_payers = companies[companies['Dividend Yield'].notna()]
pct_paying = round(len(dividend_payers) / len(companies) * 100, 1)
pct_paying
Part 12: Real Highest Dividend Yields#
top_yield = dividend_payers.nlargest(10, 'Dividend Yield')[['Symbol', 'Name', 'Sector', 'Dividend Yield']]
top_yield
Part 13: Real Earnings Per Share Distribution#
eps_clean = companies.dropna(subset=['Earnings/Share'])
negative_eps_count = (eps_clean['Earnings/Share'] < 0).sum()
negative_eps_count
Part 14: Real Companies with Negative Earnings#
unprofitable = eps_clean[eps_clean['Earnings/Share'] < 0][['Symbol', 'Name', 'Sector', 'Earnings/Share']]
unprofitable.sort_values('Earnings/Share').head(10)
Part 15: Real Price-to-Book Ratio#
pb_clean = companies.dropna(subset=['Price/Book'])
pb_clean['Price/Book'].describe().round(2)
Part 16: Real Negative Book Value Companies#
negative_book = pb_clean[pb_clean['Price/Book'] < 0][['Symbol', 'Name', 'Price/Book']]
len(negative_book)
Part 17: Real Correlation Among Valuation Metrics#
metric_cols = ['Price/Earnings', 'Price/Sales', 'Price/Book', 'Dividend Yield']
metric_corr = companies[metric_cols].corr()
metric_corr.round(3)
Part 18: Real Value Stock Screen#
value_screen = clean[(clean['Price/Earnings'] < 15) & (clean['Price/Earnings'] > 0) & (clean['Price/Book'] < 3)]
len(value_screen)
Part 19: Real Value Screen Results#
value_screen[['Symbol', 'Name', 'Sector', 'Price/Earnings', 'Price/Book']].sort_values('Price/Earnings').head(10)
Part 20: Real Market Cap Buckets#
cap_bins = [0, 10e9, 50e9, 200e9, np.inf]
cap_labels = ['Small (<$10B)', 'Mid ($10-50B)', 'Large ($50-200B)', 'Mega (>$200B)']
clean['CapTier'] = pd.cut(clean['Market Cap'], bins=cap_bins, labels=cap_labels)
clean['CapTier'].value_counts()
Part 21: Real Valuation by Market Cap Tier#
tier_pe = clean.groupby('CapTier', observed=True)['Price/Earnings'].mean()
tier_pe.round(2)
Part 22: Real EBITDA Margin Proxy#
ebitda_clean = clean.dropna(subset=['EBITDA'])
ebitda_to_cap = (ebitda_clean['EBITDA'] / ebitda_clean['Market Cap'])
ebitda_to_cap.describe().round(4)
Part 23: Real Highest Cash-Generation Efficiency#
ebitda_clean = ebitda_clean.assign(EBITDA_to_Cap=ebitda_to_cap)
ebitda_clean.nlargest(10, 'EBITDA_to_Cap')[['Symbol', 'Name', 'EBITDA_to_Cap']]
Part 24: Visualizing Real Sector Valuation Spread#
top_sectors_by_count = clean['Sector'].value_counts().head(6).index
plt.figure(figsize=(11, 6))
plot_data = [clean.loc[clean['Sector'] == s, 'Price/Earnings'].clip(upper=80) for s in top_sectors_by_count]
plt.boxplot(plot_data, tick_labels=top_sectors_by_count)
plt.xticks(rotation=30, ha='right')
plt.ylabel('Real Price-to-Earnings Ratio')
plt.title('Real P/E Spread Across Top Sectors')
plt.tight_layout()
plt.savefig('sector_pe_boxplot.png', dpi=120)
plt.close()
Part 25: Real 52-Week Range Analysis#
range_clean = companies.dropna(subset=['52 Week Low', '52 Week High', 'Price'])
range_clean = range_clean.assign(PctFromHigh=(range_clean['Price'] - range_clean['52 Week High']) / range_clean['52 Week High'] * 100)
range_clean['PctFromHigh'].describe().round(1)
Part 26: Real Stocks Near Their 52-Week Low#
near_low = range_clean.nsmallest(10, 'PctFromHigh')[['Symbol', 'Name', 'Price', '52 Week High', 'PctFromHigh']]
near_low.round(2)
Part 27: Saving the Real Cleaned Fundamentals Table#
clean.to_csv('sp500_fundamentals_clean.csv', index=False)
reloaded = pd.read_csv('sp500_fundamentals_clean.csv')
reloaded.shape[0] == clean.shape[0]
Part 28: Real Sanity Check on Value Screen#
(value_screen['Price/Earnings'] < 15).all() and (value_screen['Price/Book'] < 3).all()
Part 29: Real Sector Diversity Check#
value_screen_sectors = value_screen['Sector'].nunique()
value_screen_sectors
Part 30: Real Recap Print#
print(f'Across {len(companies)} real S&P 500 companies, {pct_paying}% pay a dividend, {negative_eps_count} reported negative earnings, and {len(value_screen)} passed a simple real value screen across {value_screen_sectors} different sectors.')
Wrap-Up: What You Learned#
- Fundamental data is a real cross-sectional snapshot, one row per company, a genuinely different shape than the time-series data used throughout the rest of this domain.
- Real missing data in financial statements is not random noise, it often reflects a genuine business reality, like a company simply not paying a dividend.
- A single real ratio like P/E never tells the whole story, pairing it with real price-to-book or real sector context gives a fuller picture.
- Grouping real companies by sector or market cap tier before comparing valuation is what makes the real comparison actually fair.
- A real simple value screen, combining multiple real filters, is the same basic logic professional analysts use, just built here from first principles.
- Next video: real Value-at-Risk and drawdown analysis, returning to time series data to quantify tail risk more rigorously.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



