Mathew K Analytics

Lesson 16 · Python for Retail E-commerce Analytics

How to Identify Missing and Invalid Retail Records Using Python for E-commerce Analytics

In this lesson, we tackle the vital issue of detecting missing and invalid data in retail and e-commerce datasets. Quality data is essential for analyzing…

⬇ 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

Identifying Missing and Invalid Retail Records#

  • In this lesson, we tackle the vital issue of detecting missing and invalid data in retail and e-commerce datasets.
  • Quality data is essential for analyzing sales, inventory, customer behavior, and making sound business decisions.
  • You will learn how to systematically find, interpret, and address gaps or errors in retail records to ensure accurate analysis.
  • Mastering these techniques helps teams avoid costly mistakes and unlock trusted sales or marketing insights.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Core Concepts in Retail Analytics Data#

  • Retail datasets reflect real-world transactions, orders, products, and customer behavior.
  • Sales data combines item quantity, unit price, and often customer or time details.
  • Missing data can obscure revenue, mask popular products, or lead to false inventory signals.
  • Common data issues include blank fields, negative values, date errors, or duplications.
  • Beginner analysts may misinterpret missing prices, mix up quantities, or overlook invalid identifiers.
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00502/online_retail_II.xlsx'
df = pd.read_excel(url, sheet_name='Year 2010-2011')
df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])
print(df.shape)
print(df.head(3))
(541910, 8)
  Invoice StockCode                         Description  Quantity  \
0  536365    85123A  WHITE HANGING HEART T-LIGHT HOLDER         6   
1  536365     71053                 WHITE METAL LANTERN         6   
2  536365    84406B      CREAM CUPID HEARTS COAT HANGER         8   

          InvoiceDate  Price  Customer ID         Country  
0 2010-12-01 08:26:00   2.55      17850.0  United Kingdom  
1 2010-12-01 08:26:00   3.39      17850.0  United Kingdom  
2 2010-12-01 08:26:00   2.75      17850.0  United Kingdom  
print(df.columns.tolist())
['Invoice', 'StockCode', 'Description', 'Quantity', 'InvoiceDate', 'Price', 'Customer ID', 'Country']
missing_summary = df.isnull().sum()
print(missing_summary)
Invoice             0
StockCode           0
Description      1454
Quantity            0
InvoiceDate         0
Price               0
Customer ID    135080
Country             0
dtype: int64
num_missing = df.isnull().sum().sum()
print(f'Total missing values in dataset: {num_missing}')
Total missing values in dataset: 136534
missing_rows = df[df.isnull().any(axis=1)]
print(f'Rows with missing data: {missing_rows.shape[0]}')
print(missing_rows.head(2))
Rows with missing data: 135080
     Invoice StockCode                      Description  Quantity  \
622   536414     22139                              NaN        56   
1443  536544     21773  DECORATIVE ROSE BATHROOM BOTTLE         1   

             InvoiceDate  Price  Customer ID         Country  
622  2010-12-01 11:52:00   0.00          NaN  United Kingdom  
1443 2010-12-01 14:32:00   2.51          NaN  United Kingdom  
df['IsIncomplete'] = df.isnull().any(axis=1)
print(df[['Invoice', 'StockCode', 'IsIncomplete']].head(5))
  Invoice StockCode  IsIncomplete
0  536365    85123A         False
1  536365     71053         False
2  536365    84406B         False
3  536365    84029G         False
4  536365    84029E         False
percent_incomplete = df['IsIncomplete'].mean() * 100
print(f'Percentage of incomplete records: {percent_incomplete:.2f}%')
Percentage of incomplete records: 24.93%
invalid_quant = df[df['Quantity'] <= 0]
print(f'Negative or zero quantity transactions: {invalid_quant.shape[0]}')
print(invalid_quant[['Invoice', 'StockCode', 'Quantity']].head(2))
Negative or zero quantity transactions: 10624
     Invoice StockCode  Quantity
141  C536379         D        -1
154  C536383    35004C        -1
invalid_price = df[df['Price'] <= 0]
print(f'Negative or zero price transactions: {invalid_price.shape[0]}')
print(invalid_price[['Invoice', 'StockCode', 'Price']].head(2))
Negative or zero price transactions: 2517
     Invoice StockCode  Price
622   536414     22139    0.0
1510  536545     21134    0.0
empty_desc = df[df['Description'].isnull() | (df['Description'].str.strip() == '')]
print(f'Records missing a product description: {empty_desc.shape[0]}')
print(empty_desc[['Invoice', 'StockCode', 'Description']].head(2))
Records missing a product description: 1454
     Invoice StockCode Description
622   536414     22139         NaN
1510  536545     21134         NaN
df_nodup = df.drop_duplicates()
print(f'Records after removing duplicates: {df_nodup.shape[0]}')
Records after removing duplicates: 536642
invalid_customer = df[df['Customer ID'].isnull() | (df['Customer ID'] == 0)]
print(f'Rows missing or invalid Customer ID: {invalid_customer.shape[0]}')
print(invalid_customer[['Invoice', 'StockCode', 'Customer ID']].head(2))
Rows missing or invalid Customer ID: 135080
     Invoice StockCode  Customer ID
622   536414     22139          NaN
1443  536544     21773          NaN
future_dates = df[df['InvoiceDate'] > pd.Timestamp('2011-12-31')]
print(f'Transactions with future invoice dates: {future_dates.shape[0]}')
print(future_dates[['Invoice', 'InvoiceDate']].head(1))
Transactions with future invoice dates: 0
Empty DataFrame
Columns: [Invoice, InvoiceDate]
Index: []

Best Practices in Retail Data Cleaning#

  • Always start by profiling missing and invalid data before any deeper sales or customer analysis.
  • Flag, review, and document how you handle each type of data issue, keeping records auditable.
  • Automate removal or fixing of negatives, blanks, and duplicates where possible.
  • Prioritize crucial fields: dates, product info, customer IDs, and financial columns.
  • Consistently re-check your cleaned data for hidden or indirect errors affecting business results.
np.random.seed(42)
n_orders = 1000
order_ids = list(range(1, n_orders+1))
customer_ids = np.random.randint(1000, 1500, n_orders)
product_ids = np.random.randint(1001, 1100, n_orders)
quantities = np.random.randint(-2, 5, n_orders)
order_dates = pd.date_range('2023-01-01', periods=n_orders, freq='h')
df_orders = pd.DataFrame({'OrderID': order_ids, 'CustomerID': customer_ids, 'ProductID': product_ids, 'Quantity': quantities, 'OrderDate': order_dates})
df_orders.loc[::50, 'Quantity'] = np.nan  # insert some nulls
print(df_orders.head(5))
   OrderID  CustomerID  ProductID  Quantity           OrderDate
0        1        1102       1049       NaN 2023-01-01 00:00:00
1        2        1435       1011       2.0 2023-01-01 01:00:00
2        3        1348       1085       0.0 2023-01-01 02:00:00
3        4        1270       1026       2.0 2023-01-01 03:00:00
4        5        1106       1063      -1.0 2023-01-01 04:00:00
issues = df_orders[(df_orders['Quantity'].isnull()) | (df_orders['Quantity'] <= 0)]
print(f'Total problematic order records: {issues.shape[0]}')
print(issues.head(3))
Total problematic order records: 423
   OrderID  CustomerID  ProductID  Quantity           OrderDate
0        1        1102       1049       NaN 2023-01-01 00:00:00
2        3        1348       1085       0.0 2023-01-01 02:00:00
4        5        1106       1063      -1.0 2023-01-01 04:00:00
df_clean = df_orders.dropna(subset=['Quantity'])
df_clean = df_clean[df_clean['Quantity'] > 0]
print(f'Remaining clean orders: {df_clean.shape[0]}')
Remaining clean orders: 577

End-to-End Example: Flag and Analyze Top-Selling Product#

  • Now, we will identify and interpret the top-selling product by cleaned quantity from our simulated orders.
  • This approach ensures that recommendations are based only on valid, trustworthy transactions.
  • The outcome helps retailers focus stocking or marketing efforts on successful products and avoid out-of-stock issues.
top_product = df_clean.groupby('ProductID')['Quantity'].sum().idxmax()
top_sales = df_clean.groupby('ProductID')['Quantity'].sum().max()
print(f'Top-selling product is {top_product} with total quantity sold: {top_sales}')
Top-selling product is 1038 with total quantity sold: 33.0

Lesson Recap and Next Steps#

  • Detecting and handling missing or invalid retail records is crucial to ensure data-driven business decisions are accurate and trusted.
  • Cleaning your datasets before analysis prevents costly mistakes in sales, stock, or customer targeting.
  • Practice these techniques regularly and automate your checks for large retail systems.
  • Explore more advanced retail analytics skills in upcoming lessons to further strengthen your business value.

Found this useful?

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