Mathew K Analytics

Lesson 11 · Finance and Stock Market Analytics

Structure of Stock Market Datasets for Analytical Training

This lesson covers the structure of real stock market data you will use as a finance analyst. In finance and stock market work, understanding how data is…

⬇ Download notebookOpen in Colab ↗

📓 Full notebook

Download .ipynb

Structure of Stock Market Datasets#

  • This lesson covers the structure of real stock market data you will use as a finance analyst.
  • In finance and stock market work, understanding how data is organised is essential for real-world analysis.
  • You will learn to recognise and manipulate different types of real market datasets.
  • We will cover multiple real stock market data formats, their meanings, and common mistakes beginners make.
  • By the end, you will confidently explore, reshape, and work with real financial data for analysis tasks.
import pandas as pd
import numpy as np
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')

Understanding the Structure of Stock Market Data#

  • Stock market data comes in many forms: daily prices, financial fundamentals, and trading signals.
  • Some datasets have one row per company; some have one row per date and ticker.
  • Common columns are Date, Ticker, Open, Close, Volume, and sometimes financial ratios like P/E.
  • Beginners often get confused by MultiIndex columns or reshaped (pivoted) dataframes.
  • Learning to quickly interpret these structures is vital for analysis and modeling.
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

Data Structure: Long Format (One Row per Ticker/Date)#

  • Each record now shows: date, ticker, open price, high, low, close, volume.
  • This 'long format' is perfect for time series analysis and groupby statistics.
  • Example: To filter for just AAPL, use ohlcv[ohlcv['Ticker'] == 'AAPL'].
  • Beginners sometimes expect each company to be a single column do not be fooled!
aapl = ohlcv[ohlcv['Ticker'] == 'AAPL']
print(aapl.head(3))
         Date Ticker       Close        High         Low        Open    Volume
0  2025-07-14   AAPL  207.795639  210.076599  206.719905  209.100460  38840100
5  2025-07-15   AAPL  208.283707  211.052720  208.094455  208.393273  42296300
10 2025-07-16   AAPL  209.329544  211.560683  207.815546  209.468990  47490500
ohlcv_wide = ohlcv.pivot(index='Date', columns='Ticker', values='Close')
print(ohlcv_wide.head(3))
Ticker            AAPL        AMZN       GOOGL        MSFT        TSLA
Date                                                                  
2025-07-14  207.795639  225.690002  181.043518  499.033936  316.899994
2025-07-15  208.283707  226.350006  181.482285  501.811737  310.779999
2025-07-16  209.329544  223.190002  182.449524  501.613312  321.670013

Example: Stock Prices (Wide Format)#

  • Next, let us look at a stock price dataset: one row per date, columns for each ticker.
  • This format is ideal for rolling windows, correlations, or visualising comparisons.
  • Beware: If a dataset comes from yfinance with multiple tickers, column names are sorted alphabetically.
tickers = ['AAPL','MSFT','GOOGL','AMZN','TSLA']
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.033905  316.899994
1 2025-07-15  208.283691  226.350006  181.482285  501.811768  310.779999
2 2025-07-16  209.329544  223.190002  182.449509  501.613312  321.670013
first_day = df.iloc[0]
print(first_day)
Date     2025-07-14 00:00:00
AAPL              207.795609
AMZN              225.690002
GOOGL             181.043518
MSFT              499.033905
TSLA              316.899994
Name: 0, dtype: object
sp500_url = 'https://raw.githubusercontent.com/datasets/s-and-p-500-companies/main/data/constituents.csv'
sp500_df = pd.read_csv(sp500_url)
print(sp500_df.shape)
print(sp500_df.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  

S&P 500 Company List: One Row Per Company#

  • This dataset shows one row per company, not per date.
  • Typical columns: ticker symbol, company name, sector, headquarter location, and more.
  • Such lists are critical for sector analysis, universe filtering, or mapping tickers to industries.
tech_companies = sp500_df[sp500_df['GICS Sector'] == 'Information Technology']
print(tech_companies[['Symbol', 'Security']].head(3))
  Symbol                Security
4    ACN               Accenture
5   ADBE              Adobe Inc.
6    AMD  Advanced Micro Devices
portfolio_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(portfolio_tickers, period='1y', auto_adjust=True, progress=False)['Close']
rows = []
for tk in portfolio_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_portfolio = pd.DataFrame(rows)
df_portfolio['gain_pct'] = np.round((df_portfolio['cur_price'] - df_portfolio['buy_price']) / df_portfolio['buy_price'] * 100, 2)
print(df_portfolio.shape)
print(df_portfolio.head(3))
(10, 6)
  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

Portfolio Dataset: Simulated Positions Using Real Prices#

  • Here, each row is a different stock held in a portfolio, with real buy and current prices.
  • Random share counts simulate a real investor's positions.
  • Columns include ticker, shares, sector, purchase price, latest price, and percentage gain.
  • This format helps analyse portfolio performance, risk, and sector exposures.
  • The gain_pct is computed using the real buy and end prices (not randomly made up).
biggest_win = df_portfolio.sort_values('gain_pct', ascending=False).iloc[0]
print(biggest_win)
ticker        GOOGL
shares           19
buy_price    181.04
cur_price    352.51
sector         Tech
gain_pct      94.71
Name: 2, dtype: object
fundamental_tickers = ['AAPL','MSFT','GOOGL','AMZN','TSLA','NVDA','META','JPM','JNJ','XOM','WMT','V']
rows = []
for tk in fundamental_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')
    })
df_fundamental = pd.DataFrame(rows)
print(df_fundamental.shape)
print(df_fundamental.head(3))
(12, 7)
  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  

Fundamentals Dataset: Company-Level Data#

  • Here, each row is one company and columns store important financial benchmarks.
  • Typical fields: market cap, trailing P/E, EPS, profit margin, debt/equity.
  • Use such data for valuation, risk, and screening filters in finance.
  • Beginners often forget: real world data may be missing values!
spy = yf.download('SPY', period='2y', auto_adjust=True, progress=False)
df_returns = spy[['Close','Volume']].reset_index()
df_returns.columns = ['date','price','volume']
df_returns['daily_return'] = (df_returns['price'].pct_change() * 100).round(4)
df_returns = df_returns.dropna().reset_index(drop=True)
print(df_returns.shape)
print(df_returns.head(3))
(499, 4)
        date       price    volume  daily_return
0 2024-07-16  551.797058  36475300        0.5930
1 2024-07-17  544.060303  57119000       -1.4021
2 2024-07-18  539.879150  56270400       -0.7685

Returns/Time Series Data: Row per Date#

  • Common for price and returns data: each record is a specific trading date.
  • Key columns: date, price (typically close), daily return (%), and often volume.
  • Such time series are critical for backtesting and volatility analysis.
signal_data = yf.download('AAPL', period='1y', auto_adjust=True, progress=False)
signal_df = signal_data[['Close','Volume']].reset_index()
signal_df.columns = ['date','close','volume']
signal_df['sma20'] = signal_df['close'].rolling(20).mean().round(2)
signal_df['sma50'] = signal_df['close'].rolling(50).mean().round(2)
signal_df['signal'] = np.where(signal_df['sma20'] > signal_df['sma50'], 1, -1)
print(signal_df.shape)
print(signal_df.tail(3))
(251, 6)
          date       close    volume   sma20   sma50  signal
248 2026-07-09  316.220001  48124500  296.92  296.83       1
249 2026-07-10  315.320007  34132300  298.10  297.73       1
250 2026-07-13  317.309998  41376714  299.19  298.67       1

Advanced: Error Handling with Real Datasets#

  • Real financial datasets may have missing prices, nulls, or bad types.
  • Always check for NaN values when performing aggregations or calculations.
  • Watch for sudden changes in column names or order, especially after downloading new data.
nan_count = signal_df.isna().sum()
print(nan_count)
date       0
close      0
volume     0
sma20     19
sma50     49
signal     0
dtype: int64
clean_df = signal_df.dropna().reset_index(drop=True)
print(clean_df.shape)
print(clean_df.head(2))
(202, 6)
        date       close     volume   sma20   sma50  signal
0 2025-09-22  255.357559  105517400  235.12  224.10       1
1 2025-09-23  253.712234   60275200  236.48  225.02       1

Best Practices for Working with Stock Market Datasets#

  • Always examine the row/column structure before any analysis.
  • Use .info(), .head(), and .describe() often to preview data types and ranges.
  • Set np.random.seed(42) for any simulated portfolio positions to ensure reproducibility.
  • Document column meanings if processing many datasets.
  • Never guess ticker order after yfinance downloads: always verify.
print(clean_df.info())
print(signal_df.describe())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 202 entries, 0 to 201
Data columns (total 6 columns):
 #   Column  Non-Null Count  Dtype         
---  ------  --------------  -----         
 0   date    202 non-null    datetime64[ns]
 1   close   202 non-null    float64       
 2   volume  202 non-null    int64         
 3   sma20   202 non-null    float64       
 4   sma50   202 non-null    float64       
 5   signal  202 non-null    int64         
dtypes: datetime64[ns](1), float64(3), int64(2)
memory usage: 9.6 KB
None
                                date       close        volume       sma20  \
count                            251  251.000000  2.510000e+02  232.000000   
mean   2026-01-09 15:58:05.258964224  262.715183  5.044064e+07  263.194828   
min              2025-07-14 00:00:00  201.580292  1.791060e+07  210.710000   
25%              2025-10-09 12:00:00  249.799858  3.941050e+07  253.475000   
50%              2026-01-09 00:00:00  263.507233  4.567730e+07  263.130000   
75%              2026-04-11 12:00:00  275.692795  5.274305e+07  274.787500   
max              2026-07-13 00:00:00  317.309998  2.617755e+08  304.660000   
std                              NaN   25.897752  2.302662e+07   21.878730   

            sma50      signal  
count  202.000000  251.000000  
mean   263.520743    0.155378  
min    224.100000   -1.000000  
25%    259.975000   -1.000000  
50%    264.435000    1.000000  
75%    270.292500    1.000000  
max    298.670000    1.000000  
std     15.295365    0.989829  
# End-to-End Example: Portfolio Gain by Sector
by_sector = df_portfolio.groupby('sector')['gain_pct'].mean().round(2)
print(by_sector)
sector
Auto        24.57
Consumer     9.58
Finance     18.09
Health      68.48
Media      -41.49
Tech        28.28
Name: gain_pct, dtype: float64

For More Learning#

  • Review this notebook regularly to reinforce how financial data is structured.
  • Explore real datasets on Yahoo Finance or GitHub, and practice inspecting their columns and row layouts.
  • Check out additional guides on the official pandas documentation or YouTube for deeper skills.

Found this useful?

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