Mathew K Analytics

Lesson 47 · Market Research Analytics in Python

Analyzing Market and Consumer Trends Over Time Using Python | Market Research Analytics

We will learn how to analyze real customer and market research data over time. This helps businesses spot changing trends in customer satisfaction, loyalty,…

⬇ 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

Analyzing Market and Consumer Trends Over Time#

  • We will learn how to analyze real customer and market research data over time.
  • This helps businesses spot changing trends in customer satisfaction, loyalty, and behavior.
  • Learners will discover how to uncover patterns that lead to smarter, data-driven decisions.
  • Step-by-step, we will explore and visualize true market research datasets.
  • By the end, you will know how to spot trends and present actionable insights.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import openml
import warnings
warnings.filterwarnings('ignore')

Core Concepts for Market Trend Analysis#

  • Market research datasets often collect responses over time, linked to customer demographics and actions.
  • Data can include survey ratings, purchases, NPS or satisfaction scores, and demographic details.
  • Beginners often forget to sort by date, mislabel categorical fields, or treat scores as continuous instead of categorical.
  • Correct trend analysis requires attention to missing values and valid groupings.
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00502/online_retail_II.xlsx'
df_retail = pd.read_excel(url, sheet_name='Year 2010-2011')
df_retail['InvoiceDate'] = pd.to_datetime(df_retail['InvoiceDate'])
print('Shape:', df_retail.shape)
print(df_retail[['Invoice', 'InvoiceDate', 'Price']].head(3))
Shape: (541910, 8)
  Invoice         InvoiceDate  Price
0  536365 2010-12-01 08:26:00   2.55
1  536365 2010-12-01 08:26:00   3.39
2  536365 2010-12-01 08:26:00   2.75
df_retail['YearMonth'] = df_retail['InvoiceDate'].dt.to_period('M')
monthly_sales = df_retail.groupby('YearMonth')['Price'].sum()
print(monthly_sales.head(5))
YearMonth
2010-12    260520.850
2011-01    172752.800
2011-02    127448.770
2011-03    171486.510
2011-04    129164.961
Freq: M, Name: Price, dtype: float64
plt.figure(figsize=(10,4))
monthly_sales.plot(marker='o')
plt.title('Monthly Online Retail Sales Trends')
plt.ylabel('Total Sales')
plt.xlabel('Year-Month')
plt.tight_layout()
plt.show()
No description has been provided for this image
dataset = openml.datasets.get_dataset(42178)
df_satisfaction, _, _, _ = dataset.get_data(dataset_format='dataframe')
print(df_satisfaction[['tenure', 'MonthlyCharges', 'Churn']].head(3))
   tenure  MonthlyCharges Churn
0       1           29.85    No
1      34           56.95    No
2       2           53.85   Yes
tenure_churn = df_satisfaction.groupby('tenure')['Churn'].value_counts(normalize=True).unstack().fillna(0)
tenure_churn_pct = tenure_churn * 100
print(tenure_churn_pct.head(10))
Churn           No        Yes
tenure                       
0       100.000000   0.000000
1        38.009788  61.990212
2        48.319328  51.680672
3        53.000000  47.000000
4        52.840909  47.159091
5        51.879699  48.120301
6        63.636364  36.363636
7        61.068702  38.931298
8        65.853659  34.146341
9        61.344538  38.655462
plt.figure(figsize=(8,5))
plt.plot(tenure_churn_pct.index, tenure_churn_pct.get('Yes', 0), label='Churned')
plt.plot(tenure_churn_pct.index, tenure_churn_pct.get('No', 0), label='Retained')
plt.xlabel('Customer Tenure (months)')
plt.ylabel('Percentage')
plt.title('Churn vs Retention Rates by Tenure')
plt.legend()
plt.tight_layout()
plt.show()
No description has been provided for this image
df_retail['Year'] = df_retail['InvoiceDate'].dt.year
country_year = df_retail.groupby(['Country', 'Year'])['Price'].sum().unstack()
top_countries = country_year.sum(axis=1).sort_values(ascending=False).head(5).index
display(country_year.loc[top_countries])
Year 2010 2011
Country
United Kingdom 251922.90 1993792.574
EIRE 1973.13 46474.060
France 1557.36 41492.630
Germany 2181.67 35484.330
Singapore NaN 25108.890
sns.heatmap(country_year.loc[top_countries], annot=True, fmt='.0f', cmap='Blues')
plt.title('Annual Sales Heatmap for Top 5 Countries')
plt.xlabel('Year')
plt.ylabel('Country')
plt.tight_layout()
plt.show()
No description has been provided for this image
import calendar
df_retail['Month'] = df_retail['InvoiceDate'].dt.month
monthly_avg = df_retail.groupby(['Month'])['Price'].mean()
plt.bar([calendar.month_abbr[m] for m in monthly_avg.index], monthly_avg.values)
plt.title('Average Transaction Amount by Month')
plt.xlabel('Month')
plt.ylabel('Average Transaction Amount')
plt.tight_layout()
plt.show()
No description has been provided for this image
import random
random.seed(42)
sample_ids = random.sample(list(df_retail['Customer ID'].dropna().unique()), 3)
df_sample = df_retail[df_retail['Customer ID'].isin(sample_ids)]
for cid, group in df_sample.groupby('Customer ID'):
    plt.plot(group['InvoiceDate'], group['Price'].cumsum(), label=f'Customer {int(cid)}')
plt.legend()
plt.title('Cumulative Spending Over Time for Sample Customers')
plt.xlabel('Date')
plt.ylabel('Cumulative Spend')
plt.tight_layout()
plt.show()
No description has been provided for this image
dataset = openml.datasets.get_dataset(1461)
df_campaign, _, _, _ = dataset.get_data(dataset_format='dataframe')
df_campaign.columns = ['age','job','marital','education','default','balance','housing','loan','contact','day','month','duration','campaign','pdays','previous','poutcome','response']
print(df_campaign[['month', 'campaign', 'response']].head())
  month  campaign response
0   may         1        1
1   may         1        1
2   may         1        1
3   may         1        1
4   may         1        1
campaign_month = df_campaign.groupby('month')['response'].value_counts().unstack().fillna(0)
campaign_month['total'] = campaign_month.sum(axis=1)
campaign_month['response_rate'] = 100 * campaign_month.get('yes', 0) / campaign_month['total']
campaign_month_response = campaign_month[['response_rate']]
print(campaign_month_response)
response  response_rate
month                  
apr                 0.0
aug                 0.0
dec                 0.0
feb                 0.0
jan                 0.0
jul                 0.0
jun                 0.0
mar                 0.0
may                 0.0
nov                 0.0
oct                 0.0
sep                 0.0
plt.plot(campaign_month_response.index, campaign_month_response['response_rate'], marker='o')
plt.title('Monthly Marketing Campaign Response Rate')
plt.xlabel('Month')
plt.ylabel('Response Rate (%)')
plt.tight_layout()
plt.show()
No description has been provided for this image
np.random.seed(42)
df_nps = pd.DataFrame({
    'CustomerID': range(1, 501),
    'Age': np.random.randint(18, 70, 500),
    'Region': np.random.choice(['North','South','East','West'],500),
    'NPS_Score': np.random.randint(0,11,500),
    'Survey_Month': np.random.choice(['2022-06','2022-07','2022-08','2022-09'], 500)
})
print(df_nps.head(3))
   CustomerID  Age Region  NPS_Score Survey_Month
0           1   56   West          2      2022-08
1           2   69  North          0      2022-06
2           3   46   East          4      2022-07
def nps_category(score):
    if score >= 9:
        return 'Promoter'
    elif score >= 7:
        return 'Passive'
    else:
        return 'Detractor'
df_nps['NPS_Category'] = df_nps['NPS_Score'].apply(nps_category)
monthly_nps = df_nps.groupby('Survey_Month')['NPS_Category'].value_counts().unstack().fillna(0)
monthly_nps_pct = monthly_nps.div(monthly_nps.sum(axis=1), axis=0) * 100
print(monthly_nps_pct.round(1))
NPS_Category  Detractor  Passive  Promoter
Survey_Month                              
2022-06            65.5     16.6      17.9
2022-07            62.2     18.9      18.9
2022-08            63.7     16.1      20.2
2022-09            67.3     23.1       9.6
plt.figure(figsize=(7,5))
monthly_nps_pct.plot(kind='bar', stacked=True, colormap='Set2', ax=plt.gca())
plt.title('NPS Categories by Survey Month')
plt.ylabel('Percentage of Respondents')
plt.xlabel('Survey Month')
plt.tight_layout()
plt.show()
No description has been provided for this image
df_cohort = pd.DataFrame({
    'CustomerID': np.random.randint(1000,2000,24),
    'Signup_Month': pd.date_range('2021-01-01', periods=24, freq='M'),
    'Active_Users': np.random.randint(50,300,24)
})
df_cohort['Signup_Month'] = df_cohort['Signup_Month'].dt.to_period('M')
print(df_cohort.head(3))
   CustomerID Signup_Month  Active_Users
0        1855      2021-01           117
1        1364      2021-02           268
2        1674      2021-03           241
plt.plot(df_cohort['Signup_Month'].astype(str), df_cohort['Active_Users'], marker='o')
plt.title('Active Users Per Cohort Over Time')
plt.xlabel('Signup Month')
plt.ylabel('Active Users')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
No description has been provided for this image
plt.bar(df_cohort['Signup_Month'].astype(str), df_cohort['Active_Users'])
plt.title('Monthly New Active Users (Cohort Analysis)')
plt.xlabel('Cohort Signup Month')
plt.ylabel('Active Users')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
No description has been provided for this image
# Introduce artificial missing values
df_nps_missing = df_nps.copy()
df_nps_missing.loc[df_nps_missing.sample(frac=0.1, random_state=42).index, 'NPS_Score'] = np.nan
print(df_nps_missing['NPS_Score'].isna().sum(), 'NPS scores missing')
50 NPS scores missing
# Grouping error: try grouping by wrong field
try:
    wrong_group = df_retail.groupby('RandomField')['Price'].sum()
except KeyError as e:
    print('Error:', e)
Error: 'RandomField'
# Misinterpretation of NPS score as continuous value
avg_score = df_nps['NPS_Score'].mean()
print('Average NPS score (not recommended as key metric):', round(avg_score,2))
Average NPS score (not recommended as key metric): 4.89

Best Practices in Market Research Data Analysis#

  • Use segmentation to split market or customer groups for deeper insight.
  • Cross-tabulate responses against key factors (demographics, purchase size) to spot trends.
  • Construct indices or scores like NPS, loyalty, or satisfaction for quick benchmarking.
  • Always examine trends over multiple periods, not just as point-in-time numbers.
# Age group segmentation example
bins = [17, 29, 39, 49, 59, 70]
labels = ['18-29', '30-39', '40-49', '50-59', '60-70']
df_nps['Age_Group'] = pd.cut(df_nps['Age'], bins=bins, labels=labels)
seg_nps = df_nps.groupby('Age_Group')['NPS_Category'].value_counts(normalize=True).unstack()
print(seg_nps.round(2))
NPS_Category  Detractor  Passive  Promoter
Age_Group                                 
18-29              0.67     0.16      0.17
30-39              0.64     0.17      0.19
40-49              0.69     0.19      0.13
50-59              0.68     0.14      0.19
60-70              0.54     0.27      0.19
# Market index construction example
monthly_sales_index = 100 * monthly_sales / monthly_sales.iloc[0]
plt.plot(monthly_sales.index.astype(str), monthly_sales_index, marker='o')
plt.title('Indexed Sales Trend (Base Month = 100)')
plt.xlabel('Year-Month')
plt.ylabel('Sales Index (Base=100)')
plt.tight_layout()
plt.show()
No description has been provided for this image
# End-to-end: Identify month with biggest NPS improvement
monthly_nps['Net_NPS'] = (monthly_nps['Promoter'] - monthly_nps['Detractor']) / monthly_nps.sum(axis=1) * 100
print(monthly_nps[['Net_NPS']].round(1))
best_change = monthly_nps['Net_NPS'].diff().idxmax()
print('Month with biggest NPS improvement:', best_change)
NPS_Category  Net_NPS
Survey_Month         
2022-06         -47.6
2022-07         -43.3
2022-08         -43.5
2022-09         -57.7
Month with biggest NPS improvement: 2022-07

Congratulations on Completing Market Trend Analysis!#

  • You now know how to analyze market research and consumer trends over time using real data.
  • The power to spot business shifts and customer needs is in your hands.
  • Practice on your own datasets, and remember, continuous learning makes you a stronger market analyst.
  • For more advanced tutorials, search for "market research Python analytics" on YouTube and subscribe for weekly lessons!

Found this useful?

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