Mathew K Analytics

Lesson 20 · Finance and Stock Market Analytics

Preparing Clean Financial Datasets for Analysis: Step-by-Step Data Cleaning Guide

Learn how to get and prepare real financial data. Discover how messy data can hurt stock market analysis. Master data cleaning, fixing problems, and best…

📓 Full notebook

Download .ipynb

Preparing Clean Financial Datasets for Analysis#

  • Learn how to get and prepare real financial data.
  • Discover how messy data can hurt stock market analysis.
  • Master data cleaning, fixing problems, and best practices.
  • See examples from the real world to improve your skills.
  • Build confidence to solve real finance problems yourself.
import pandas as pd
import numpy as np
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')

Key Financial Data Types for Stock Market Analysis#

  • Most financial datasets come from real trading data or company reports.
  • Common types: price history, trade volume, company fundamentals.
  • They can be wide (dates as rows, tickers as columns) or long (each trade/ticker in a row).
  • Often contain missing values, bad labels, or odd formats.
  • Cleaning is vital to prevent mistakes and get real insight.

Beginner Example 1: Downloading Multi-Ticker OHLCV Data#

  • Let us download real daily prices for several tech stocks using yfinance.
  • We will reshape and preview the data in a clean, easy-to-use long format.
tickers = ['AAPL','MSFT','GOOGL','AMZN','TSLA']
ohlcv = yf.download(tickers, period='1y', auto_adjust=True, progress=False)
ohlcv = ohlcv.stack(future_stack=True).rename_axis(['Date','Ticker']).reset_index()
ohlcv.columns.name = None
print(ohlcv.shape)
print(ohlcv.head(3))
(1255, 7)
        Date Ticker       Close        High         Low        Open    Volume
0 2025-07-14   AAPL  207.795639  210.076599  206.719905  209.100460  38840100
1 2025-07-14   AMZN  225.690002  226.660004  224.240005  225.070007  35702600
2 2025-07-14  GOOGL  181.043518  183.147516  179.168861  180.495080  32536600

Beginner Example 2: Getting Only Adjusted Closing Prices#

  • Extract only daily adjusted closing prices to keep things simple.
  • The result is a convenient wide format with one column per ticker.
df = yf.download(tickers, period='1y', auto_adjust=True, progress=False)
df = df['Close'].reset_index()
df.columns.name = None
print(df.shape)
print(df.head(3))
(251, 6)
        Date        AAPL        AMZN       GOOGL        MSFT        TSLA
0 2025-07-14  207.795609  225.690002  181.043518  499.033936  316.899994
1 2025-07-15  208.283691  226.350006  181.482269  501.811768  310.779999
2 2025-07-16  209.329544  223.190002  182.449524  501.613373  321.670013

Beginner Example 3: Downloading Company List (S&P 500)#

  • Real analysis often starts by researching lists of companies.
  • Load the S&P 500 company list directly from GitHub.
  • See basic columns like ticker symbol and sector.
url = 'https://raw.githubusercontent.com/datasets/s-and-p-500-companies/main/data/constituents.csv'
sp500 = pd.read_csv(url)
print(sp500.shape)
print(sp500.head(3))
(503, 8)
  Symbol             Security  GICS Sector         GICS Sub-Industry  \
0    MMM                   3M  Industrials  Industrial Conglomerates   
1    AOS          A. O. Smith  Industrials         Building Products   
2    ABT  Abbott Laboratories  Health Care     Health Care Equipment   

     Headquarters Location  Date added    CIK Founded  
0    Saint Paul, Minnesota  1957-03-04  66740    1902  
1     Milwaukee, Wisconsin  2017-07-26  91142    1916  
2  North Chicago, Illinois  1957-03-04   1800    1888  

Intermediate Example 1: Cleaning and Filtering One Ticker#

  • Select Apple data only and check for missing trades.
  • Real data sometimes has gaps due to holidays or suspensions.
aapl = ohlcv[ohlcv['Ticker'] == 'AAPL'].copy()
print(aapl.isna().sum())
aapl = aapl.dropna()
print('Remaining rows after dropna:', len(aapl))
Date      0
Ticker    0
Close     0
High      0
Low       0
Open      0
Volume    0
dtype: int64
Remaining rows after dropna: 251

Intermediate Example 2: Calculating Returns and Checking NaN#

  • Add percent daily return to a wide price table.
  • Identify where returns are missing or invalid (NaN).
price_df = df.copy()
price_df['AAPL_ret'] = price_df['AAPL'].pct_change() * 100
print(price_df[['Date','AAPL','AAPL_ret']].head())
nan_count = price_df['AAPL_ret'].isna().sum()
print('Missing daily returns:', nan_count)
        Date        AAPL  AAPL_ret
0 2025-07-14  207.795609       NaN
1 2025-07-15  208.283691  0.234886
2 2025-07-16  209.329544  0.502129
3 2025-07-17  209.190109 -0.066610
4 2025-07-18  210.345505  0.552318
Missing daily returns: 1

Intermediate Example 3: Merging Company Metadata with Prices#

  • Join S&P 500 company info with price data by ticker.
  • Add sector details to each price row.
meta = sp500[['Symbol','GICS Sector']].rename(columns={'Symbol':'Ticker','GICS Sector':'Sector'})
aug_ohlcv = ohlcv.merge(meta, how='left', on='Ticker')
print(aug_ohlcv[['Date','Ticker','Close','Sector']].head(3))
print('Rows with missing sector:', aug_ohlcv['Sector'].isna().sum())
        Date Ticker       Close                  Sector
0 2025-07-14   AAPL  207.795639  Information Technology
1 2025-07-14   AMZN  225.690002  Consumer Discretionary
2 2025-07-14  GOOGL  181.043518  Communication Services
Rows with missing sector: 0

Advanced Example 1: Cleaning a Simulated Real Portfolio#

  • Build a portfolio with real buy prices, current prices, and random share counts.
  • Calculate percent gain and practice cleaning sector values.
tickers = ['AAPL','MSFT','GOOGL','AMZN','TSLA','NVDA','META','NFLX','JPM','JNJ']
sector_map = {'AAPL':'Tech','MSFT':'Tech','GOOGL':'Tech','AMZN':'Consumer',
              'TSLA':'Auto','NVDA':'Tech','META':'Tech','NFLX':'Media',
              'JPM':'Finance','JNJ':'Health'}
np.random.seed(42)
data = yf.download(tickers, period='1y', auto_adjust=True, progress=False)['Close']
rows = []
for tk in tickers:
    buy = round(float(data[tk].iloc[0]), 2)
    cur = round(float(data[tk].iloc[-1]), 2)
    rows.append({'ticker': tk, 'shares': int(np.random.randint(5, 100)),
                 'buy_price': buy, 'cur_price': cur, 'sector': sector_map[tk]})
df_port = pd.DataFrame(rows)
df_port['gain_pct'] = np.round((df_port['cur_price'] - df_port['buy_price']) / df_port['buy_price'] * 100, 2)
print(df_port.head(3))
  ticker  shares  buy_price  cur_price sector  gain_pct
0   AAPL      56     207.80     317.31   Tech     52.70
1   MSFT      97     499.03     390.99   Tech    -21.65
2  GOOGL      19     181.04     352.51   Tech     94.71

Advanced Example 2: Handling Missing Real Fundamental Data#

  • Download company fundamentals for several big stocks using yfinance.
  • Replace missing values with simple defaults or clearly mark them.
big_tickers = ['AAPL','MSFT','GOOGL','AMZN','TSLA','NVDA','META','JPM','JNJ','XOM','WMT','V']
rows = []
for tk in big_tickers:
    info = yf.Ticker(tk).info
    rows.append({
        'ticker':       tk,
        'sector':       info.get('sector', 'Unknown'),
        'market_cap':   info.get('marketCap'),
        'pe_ratio':     info.get('trailingPE'),
        'eps':          info.get('trailingEps'),
        'profit_margin':info.get('profitMargins'),
        'debt_equity':  info.get('debtToEquity')
    })
fund_df = pd.DataFrame(rows)
n_missing = fund_df.isnull().sum()
fund_df['debt_equity'] = fund_df['debt_equity'].fillna(-1)
print(n_missing)
print(fund_df.head(3))
ticker           0
sector           0
market_cap       0
pe_ratio         0
eps              0
profit_margin    0
debt_equity      1
dtype: int64
  ticker                  sector     market_cap   pe_ratio    eps  \
0   AAPL              Technology  4660444790784  38.415253   8.26   
1   MSFT              Technology  2904443584512  23.287073  16.79   
2  GOOGL  Communication Services  4301529022464  26.888636  13.11   

   profit_margin  debt_equity  
0        0.27152       79.548  
1        0.39342       30.271  
2        0.37919       20.026  

Advanced Example 3: Pivoting Data Formats for Analysis#

  • Change from long to wide format for matrix-based operations.
  • Use pivot() to make each ticker a separate column for quick group statistics.
wide = ohlcv.pivot(index='Date', columns='Ticker', values='Close')
print(wide.head(3))
print('NaN count per ticker:')
print(wide.isnull().sum())
Ticker            AAPL        AMZN       GOOGL        MSFT        TSLA
Date                                                                  
2025-07-14  207.795639  225.690002  181.043518  499.033936  316.899994
2025-07-15  208.283691  226.350006  181.482269  501.811737  310.779999
2025-07-16  209.329544  223.190002  182.449509  501.613342  321.670013
NaN count per ticker:
Ticker
AAPL     0
AMZN     0
GOOGL    0
MSFT     0
TSLA     0
dtype: int64

Error Handling Example 1: What If a Column is Missing?#

  • Missing columns break cleaning steps if you do not check first.
  • Always check .columns before referencing or merging columns.
if 'Close' not in ohlcv.columns:
    print('Error: Close column missing from dataset!')
else:
    print('Close column present - safe to proceed.')
Close column present - safe to proceed.

Error Handling Example 2: Handling All-NaN Rows Before Analysis#

  • Empty or all-NaN rows cause bugs in calculations or outputs.
  • Use dropna or fillna to preemptively avoid errors in later steps.
before = wide.shape[0]
wide_clean = wide.dropna(how='any')
after = wide_clean.shape[0]
print(f'Removed {before - after} rows with missing data.')
Removed 0 rows with missing data.

Best Practices: Key Principles in Financial Data Cleaning#

  • Always check for major data gaps and inspect summary statistics first.
  • Carefully handle missing values, as market datasets often have holes.
  • Avoid hard-coding tickers, columns, or index values wherever possible.
  • Save cleaned files to new locations to protect original data.
  • Clearly document each cleaning step for audit and reproducibility.
# Save your cleaned OHLCV data for future modeling or sharing
save_path = 'cleaned_ohlcv.csv'
aug_ohlcv.to_csv(save_path, index=False)
print(f'File saved: {save_path}')
File saved: cleaned_ohlcv.csv

End-to-End Example: From Raw Multi-Ticker Data to Usable Matrix#

  • From long raw format, build a complete, clean wide returns matrix for portfolio modeling.
  • Handle missing days, calculate returns, and get ready for backtests.
# Pivot long OHLCV data to wide format by close price
returns_wide = ohlcv.pivot(index='Date', columns='Ticker', values='Close')
# Compute daily percent returns for every ticker
returns = returns_wide.pct_change() * 100
# Drop first row (NaNs from pct_change), then all rows with any missing values
returns = returns.dropna().reset_index()
print(returns.head(3))
print(f'Final shape for portfolio modeling: {returns.shape}')
Ticker       Date      AAPL      AMZN     GOOGL      MSFT      TSLA
0      2025-07-15  0.234871  0.292438  0.242346  0.556636 -1.931207
1      2025-07-16  0.502129 -1.396070  0.532966 -0.039536  3.504091
2      2025-07-17 -0.066610  0.309155  0.333394  1.202486 -0.702586
Final shape for portfolio modeling: (250, 6)

YouTube Call-to-Action#

  • Want more step-by-step finance data cleaning? Search "FinPyLab Clean Data" on YouTube.
  • Like and subscribe for more real market Python tutorials.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.