My Python Report

Author

Yuli Astriani & Elisabeth Natalia Palan Bolen

Published

July 9, 2025

Presenting Coffee Survey Data Using Markdown

Check the columns header

Show code
import pandas as pd
df = pd.read_csv("../../data_sources/coffee_survey.csv")
df.columns
Index(['Unnamed: 0', 'age', 'cups', 'where_drink', 'purchase_other',
       'favourite', 'favorite_specify', 'additions', 'additions_other',
       'sweetener', 'style', 'strength', 'roast_level', 'caffeine',
       'expertise', 'coffee_a_bitterness', 'coffee_a_acidity',
       'coffee_a_personal_preference', 'coffee_b_bitterness',
       'coffee_b_acidity', 'coffee_b_personal_preference',
       'coffee_c_bitterness', 'coffee_c_acidity',
       'coffee_c_personal_preference', 'coffee_d_bitterness',
       'coffee_d_acidity', 'coffee_d_personal_preference', 'prefer_abc',
       'prefer_ad', 'prefer_overall', 'wfh', 'total_spend', 'know_source',
       'most_paid', 'most_willing', 'value_cafe', 'spent_equipment',
       'value_equipment', 'gender', 'education_level', 'employment_status',
       'number_children', 'political_affiliation'],
      dtype='object')
  • it is found that column 0 is “Unnamed”
Show code
df.rename(columns={'Unnamed: 0':'Number'}, 
inplace=True)
df.columns
Index(['Number', 'age', 'cups', 'where_drink', 'purchase_other', 'favourite',
       'favorite_specify', 'additions', 'additions_other', 'sweetener',
       'style', 'strength', 'roast_level', 'caffeine', 'expertise',
       'coffee_a_bitterness', 'coffee_a_acidity',
       'coffee_a_personal_preference', 'coffee_b_bitterness',
       'coffee_b_acidity', 'coffee_b_personal_preference',
       'coffee_c_bitterness', 'coffee_c_acidity',
       'coffee_c_personal_preference', 'coffee_d_bitterness',
       'coffee_d_acidity', 'coffee_d_personal_preference', 'prefer_abc',
       'prefer_ad', 'prefer_overall', 'wfh', 'total_spend', 'know_source',
       'most_paid', 'most_willing', 'value_cafe', 'spent_equipment',
       'value_equipment', 'gender', 'education_level', 'employment_status',
       'number_children', 'political_affiliation'],
      dtype='object')
  • Rename column 0 to “Number”

Display the favourite

Favourite types

Show code
#drop any Nan value in column "favourite"
favorites = df["favourite"].dropna()
favourites_count = favorites.value_counts()

#plot it
import matplotlib.pyplot  as plt 
favourites_count.plot(kind='bar', color='skyblue', edgecolor='black')
plt.title('Distribution of favourite coffee types')
plt.xlabel('Coffee types')
plt.ylabel('Count')
plt.show()

Favourite types by gender

Show code
#drop any Nan value in column "gender" and "favourite"
clean_df = df.dropna(subset=['favourite', 'gender'])

#groupby using sns
import seaborn as sns 
# Set Seaborn style
sns.set(style="whitegrid")

# Create the countplot
plt.figure(figsize=(10, 6))
Fig_1 = sns.countplot(data=clean_df, x='favourite', hue='gender', palette='pastel', edgecolor='gray')

# Add title and labels
plt.title('Coffee Preference by Gender')
plt.xlabel('Favourite Coffee')
plt.ylabel('Count')
plt.xticks(rotation=60, ha = 'right')
plt.tight_layout()

# Add value labels on top of each bar
for container in Fig_1.containers:
    Fig_1.bar_label(container, padding=3, fontsize=10)

plt.show()

Cups types by gender

Show code
#clean NaN value in column cup and gender

def display_table_with_gridlines(table, title="Table"):
    # Get table width
    table_str = table.to_string()
    width = len(table_str.split('\n')[0])
    
    # Print with gridlines
    print(title)
    print("=" * width)  # Header line
    print(table.to_string())
    print("=" * width)  # Bottom line

# Usage
df_clean = df.dropna(subset=["gender", "cups"])
gb = df_clean.groupby("gender")
cups_by_gender = gb["cups"].value_counts()

#plot using table
cups_table = cups_by_gender.unstack(fill_value=0)
display_table_with_gridlines(cups_table, "Coffee Cups by Gender")
Coffee Cups by Gender
====================================================================
cups                      1     2    3   4  Less than 1  More than 4
gender                                                              
Female                  366   279   57  12          133            6
Male                    751  1138  344  94          149           48
Non-binary               38    38    7   0           17            3
Other (please specify)    1     6    1   1            1            0
Prefer not to say        11    15    0   2            4            1
====================================================================

Strength types by gender

Show code
#clean NaN value in column cup and gender
df_clean = df.dropna(subset=["gender", "strength"])
gb = df_clean.groupby("gender")
strength_by_gender = gb["strength"].value_counts()
strength_table = strength_by_gender.unstack(fill_value=0)

#plot using doughnut
import matplotlib.pyplot as plt

# Create subplots for each gender
gender_categories = strength_table.index
num_genders = len(gender_categories)

fig, axes = plt.subplots(1, num_genders, figsize=(4*num_genders, 6))

# If only one gender, make axes a list
if num_genders == 1:
    axes = [axes]

# Colors for consistency across charts
colors = plt.cm.Set3(range(len(strength_table.columns)))

# Create doughnut for each gender
for i, gender in enumerate(gender_categories):
    data = strength_table.loc[gender]
    labels = strength_table.columns
    
    # Remove zero values for cleaner chart
    non_zero_data = data[data > 0]
    non_zero_labels = non_zero_data.index
    
    # Create doughnut chart
    wedges, texts, autotexts = axes[i].pie(non_zero_data, labels=non_zero_labels, 
                                          autopct='%1.1f%%', startangle=90,
                                          colors=colors[:len(non_zero_data)],
                                          wedgeprops=dict(width=0.5))  # width=0.5 makes it a doughnut
    
    axes[i].set_title(f'{gender}\n(Total: {data.sum()})', fontsize=12, fontweight='bold')

plt.suptitle('Coffee Cups Consumption by Gender', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()

Display correlation between bitterness, accidity, and personal preferences

Coffee A

Show code
#import module for statistics 
import scipy.stats as stats
from scipy.stats import pearsonr

# Drop rows with any NaN
subset_cols = ['coffee_a_bitterness', 'coffee_a_acidity', 'coffee_a_personal_preference']
df_clean = df.dropna(subset=subset_cols)

# Correlation and p-values
r_bitter, p_bitter = pearsonr(df_clean['coffee_a_bitterness'], df_clean['coffee_a_personal_preference'])
r_acid, p_acid = pearsonr(df_clean['coffee_a_acidity'], df_clean['coffee_a_personal_preference'])

# Plot
plt.figure(figsize=(10, 5))

# Bitterness vs Preference
plt.subplot(1, 2, 1)
sns.regplot(data=df_clean, x='coffee_a_bitterness', y='coffee_a_personal_preference', scatter_kws={'alpha':0.5})
plt.title(f'Bitterness vs Preference\nr={r_bitter:.2f}, p={p_bitter:.3f}')
plt.xlabel('Bitterness')
plt.ylabel('Preference')

# Acidity vs Preference
plt.subplot(1, 2, 2)
sns.regplot(data=df_clean, x='coffee_a_acidity', y='coffee_a_personal_preference', scatter_kws={'alpha':0.5})
plt.title(f'Acidity vs Preference\nr={r_acid:.2f}, p={p_acid:.3f}')
plt.xlabel('Acidity')
plt.ylabel('Preference')

plt.tight_layout()
plt.show()

Display spending

Correlation between age and total spending

Show code
# Drop any NaN values in both columns
df_clean = df.dropna(subset=['age', 'total_spend'])

# Create cross-tabulation (similar to value_counts but for two variables)
age_spend_crosstab = pd.crosstab(df_clean['age'], df_clean['total_spend'])

# Plot as heatmap (most suitable for this type of data)
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(10, 6))
sns.heatmap(age_spend_crosstab, annot=True, fmt='d', cmap='Blues', cbar_kws={'label': 'Count'})
plt.title('Age vs Total Spend Distribution')
plt.xlabel('Total Spend')
plt.ylabel('Age Group')
plt.xticks(rotation=45)
plt.yticks(rotation=0)
plt.tight_layout()
plt.show()