Mathew K Analytics

Lesson 15 · Finance and Stock Market Analytics

Assessing Financial Data Quality: Techniques and Best Practices for Accurate Analysis

Financial data quality is essential for accurate stock market analysis. Real-world data can have errors, missing values, or inconsistencies. You will learn…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Assessing Financial Data Quality#

  • Financial data quality is essential for accurate stock market analysis.
  • Real-world data can have errors, missing values, or inconsistencies.
  • You will learn how to inspect, validate, and clean stock data from real sources.
  • The goal is to ensure reliable analytics and sound financial decisions.
  • By the end, you will be able to spot and fix major data quality issues in stock datasets.
import pandas as pd
import numpy as np
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')

Understanding Our Data Sources#

  • We use real daily stock price data for major tech companies from yfinance.
  • Each row contains stock OHLCV: Open, High, Low, Close, and Volume.
  • Data comes in a 'long' format: each company and date is a row.
  • It is easy to confuse reshapingdo not flatten or pivot unless necessary.
  • Common mistake: Overwriting index or losing original tickers.
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.795624  210.076583  206.719890  209.100445  38840100
1 2025-07-14   AMZN  225.690002  226.660004  224.240005  225.070007  35702600
2 2025-07-14  GOOGL  181.043533  183.147532  179.168876  180.495095  32536600
print(ohlcv.columns.tolist())
['Date', 'Ticker', 'Close', 'High', 'Low', 'Open', 'Volume']
print('Unique tickers:', ohlcv['Ticker'].unique())
print('Date range:', ohlcv['Date'].min(), 'to', ohlcv['Date'].max())
Unique tickers: ['AAPL' 'AMZN' 'GOOGL' 'MSFT' 'TSLA']
Date range: 2025-07-14 00:00:00 to 2026-07-13 00:00:00
missing = ohlcv.isna().sum()
print('Missing values by column:')
print(missing[missing>0])
Missing values by column:
Series([], dtype: int64)
nan_rows = ohlcv[ohlcv.isna().any(axis=1)]
print('Rows with any missing data:')
print(nan_rows.head(2))
Rows with any missing data:
Empty DataFrame
Columns: [Date, Ticker, Close, High, Low, Open, Volume]
Index: []
ohlcv['Date'] = pd.to_datetime(ohlcv['Date'])
invalid_dates = ohlcv[ohlcv['Date'].isna()]
print('Rows with invalid Date:', invalid_dates.shape[0])
Rows with invalid Date: 0
dups = ohlcv.duplicated(subset=['Date','Ticker'])
print('Number of duplicate rows:', dups.sum())
Number of duplicate rows: 0
ohlcv_clean = ohlcv.drop_duplicates(subset=['Date','Ticker'])
print('Rows after removing duplicates:', ohlcv_clean.shape[0])
Rows after removing duplicates: 1255
descriptive = ohlcv_clean.describe(include='all')
print(descriptive)
                                 Date Ticker        Close         High  \
count                            1255   1255  1255.000000  1255.000000   
unique                            NaN      5          NaN          NaN   
top                               NaN   AAPL          NaN          NaN   
freq                              NaN    251          NaN          NaN   
mean    2026-01-09 15:58:05.258963968    NaN   329.794365   333.887787   
min               2025-07-14 00:00:00    NaN   181.043533   183.147532   
25%               2025-10-09 00:00:00    NaN   246.436493   249.975472   
50%               2026-01-09 00:00:00    NaN   310.260010   313.260010   
75%               2026-04-13 00:00:00    NaN   407.790009   413.622189   
max               2026-07-13 00:00:00    NaN   538.658508   551.048474   
std                               NaN    NaN    94.717124    96.037540   

                Low         Open        Volume  
count   1255.000000  1255.000000  1.255000e+03  
unique          NaN          NaN           NaN  
top             NaN          NaN           NaN  
freq            NaN          NaN           NaN  
mean     325.518656   329.794922  4.650517e+07  
min      179.168876   180.495095  5.855900e+06  
25%      244.191120   247.457831  3.041665e+07  
50%      306.100006   309.559998  4.118300e+07  
75%      399.729230   406.584247  5.517970e+07  
max      537.366702   550.830186  2.617755e+08  
std       93.466224    94.936618  2.509479e+07  
zero_vol = ohlcv_clean[ohlcv_clean['Volume'] == 0]
print('Rows with zero trading volume:', zero_vol.shape[0])
Rows with zero trading volume: 0
print('Any negative close prices:', (ohlcv_clean['Close'] < 0).any())
print('Any negative volume:', (ohlcv_clean['Volume'] < 0).any())
Any negative close prices: False
Any negative volume: False
strange = ohlcv_clean[(ohlcv_clean['High'] < ohlcv_clean['Low']) | (ohlcv_clean['Open'] < 0) | (ohlcv_clean['Low'] < 0)]
print('Rows with bad OHLC logic:', strange.shape[0])
Rows with bad OHLC logic: 0
print('Inspecting a suspicious row for business understanding:')
if strange.shape[0]>0:
    print(strange.head(1))
Inspecting a suspicious row for business understanding:

Intermediate Data Validation Checks#

    1. Are all business holidays and weekends excluded from trading data?
    1. Does every ticker have consistent records for each valid date?
    1. Is there a pattern in missingnesssuch as for new IPOs or technical outages?
pivot = ohlcv_clean.pivot(index='Date', columns='Ticker', values='Close')
date_coverage = pivot.notnull().sum(axis=1)
print('Number of tickers reported per day (any missing):')
print(date_coverage.value_counts())
Number of tickers reported per day (any missing):
5    251
Name: count, dtype: int64
piv = ohlcv_clean.pivot(index='Date', columns='Ticker', values='Close')
weekends = piv.index[piv.index.weekday >= 5]
print('Weekend entries:', len(weekends))
Weekend entries: 0
missing_per_ticker = piv.isna().sum()
print('Missing days per ticker:')
print(missing_per_ticker)
Missing days per ticker:
Ticker
AAPL     0
AMZN     0
GOOGL    0
MSFT     0
TSLA     0
dtype: int64

Quick Debugging for Data Cleaning#

  • Check total row count before and after cleaning.
  • Always summarize full and missing data per variable.
  • Save cleaned data to file and visually inspect if needed.
ohlcv_clean.to_csv('ohlcv_clean.csv', index=False)
print('Cleaned data saved to ohlcv_clean.csv')
Cleaned data saved to ohlcv_clean.csv

Best Practices for Financial Data Quality#

  • Validate every field against business logic and expected range.
  • Track all changes madeduplicates removed, rows dropped, or values replaced.
  • Never delete raw data without keeping a backup.
  • Document all assumptions and cleaning rules applied.
def clean_ohlcv(df):
    df = df.copy()
    df = df[df['Volume'] > 0]
    df = df[df['Close'] > 0]
    df = df[df['High'] >= df['Low']]
    df = df.drop_duplicates(subset=['Date','Ticker'])
    return df

ohlcv_final = clean_ohlcv(ohlcv)
print('Final cleaned rows:', ohlcv_final.shape[0])
Final cleaned rows: 1255
np.random.seed(42)  # reproducibility
random_sample = ohlcv_final.sample(n=5)
print('Random sample of high-quality records:')
print(random_sample)
Random sample of high-quality records:
           Date Ticker       Close        High         Low        Open  \
1196 2026-06-25   AMZN  227.009995  232.320007  225.550003  232.020004   
101  2025-08-11   AMZN  221.300003  223.050003  220.399994  221.779999   
51   2025-07-28   AMZN  232.789993  234.289993  232.250000  233.350006   
63   2025-07-30   MSFT  509.172943  511.861490  505.403067  511.087642   
1070 2026-05-19   AAPL  298.970001  300.510010  296.350006  296.970001   

        Volume  
1196  77674100  
101   31646200  
51    26300100  
63    26380400  
1070  42243600  

End-to-End Example: From Raw Data to Quality Analytics#

  • Raw OHLCV data is assessed, cleaned, and ready for analytics.
  • Analysts can now trust summary statistics, trends, and investment models.
  • Try applying these quality checks on any new stock data.
# Example: Compute average closing price per ticker after cleaning
avg_close = ohlcv_final.groupby('Ticker')['Close'].mean().round(2)
print('Average Close Price per Ticker:')
print(avg_close)
Average Close Price per Ticker:
Ticker
AAPL     262.72
AMZN     232.28
GOOGL    297.01
MSFT     453.95
TSLA     403.02
Name: Close, dtype: float64

Continue Your Journey#

  • Want to dive deeper into real-world finance analytics?
  • Watch more on our channel and subscribe for hands-on Python finance tutorials!

Found this useful?

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