Exercises on NumPy, Pandas, and Matplotlib
Exercise 1: Temperature Data
import numpy as np
import pandas as pd
data = {'Date': pd.date_range('2025-01-01', periods=10),
'AvgTemp': [30, 32, [Link], 28, 31, 29, [Link], 33, 35, 30]}
df = [Link](data)
temps = df['AvgTemp'].to_numpy()
mean_temp = [Link](temps)
temps = np.nan_to_num(temps, nan=mean_temp)
print("Max Temperature:", [Link](temps))
print("Min Temperature:", [Link](temps))
print("Std Deviation:", [Link](temps))
Exercise 2: Stock Prices
import pandas as pd
import numpy as np
stock = [Link]({
'Day': range(1, 31),
'Close': [Link](100, 200, 30)
})
stock['Pct_Change'] = stock['Close'].pct_change() * 100
max_increase = [Link][stock['Pct_Change'].idxmax()]
print("Highest Increase on Day:", max_increase['Day'], "→", max_increase['Pct_Change'],
"%")
stock['Norm_Close'] = (stock['Close'] - stock['Close'].min()) / (stock['Close'].max() -
stock['Close'].min())
print([Link]())
Exercise 3: Student Marks
import pandas as pd
import numpy as np
students = [Link]({
'Name': ['A', 'B', 'C', 'D', 'E'],
'Math': [78, 90, [Link], 88, 95],
'Science': [85, 92, 89, [Link], 94],
'English': [80, 86, 84, 88, [Link]]
})
[Link]([Link](numeric_only=True), inplace=True)
students['Total'] = students[['Math', 'Science', 'English']].sum(axis=1)
print([Link](5, 'Total'))
Exercise 4: E-Commerce
import pandas as pd
data = {
'CustomerID': [1, 2, 3, 1, 2, 4],
'Age': [25, 30, 22, 25, 30, 40],
'Gender': ['Male', 'Female', 'Female', 'Male', 'Female', 'Male'],
'Product': ['A', 'B', 'A', 'C', 'A', 'B'],
'Price': [100, 200, 100, 150, 100, 250]
}
df = [Link](data)
avg_spending = [Link]('Gender')['Price'].mean()
print("Average spending by gender:\n", avg_spending)
most_purchased = df['Product'].value_counts().idxmax()
print("Most Purchased Product:", most_purchased)
df = df.drop_duplicates()
overall_avg = df['Price'].mean()
high_spenders = df[df['Price'] > overall_avg]
print("Customers spending above average:\n", high_spenders)
Exercise 5: Sales Data
import pandas as pd
import [Link] as plt
sales = [Link]({
'Month': pd.date_range('2025-01-01', periods=12, freq='M'),
'Sales': [1200, 1300, 1100, 1500, 1800, 1700, 1600, 1900, 2100, 2000, 2200, 2300]
})
[Link](figsize=(8,5))
[Link](sales['Month'], sales['Sales'], marker='o', label='Monthly Sales')
[Link]('Monthly Sales Trend')
[Link]('Month')
[Link]('Sales')
[Link](True)
max_row = [Link][sales['Sales'].idxmax()]
[Link](max_row['Month'], max_row['Sales'], color='red', label='Highest Sales')
[Link]()
[Link]()
Exercise 6: Sports Preferences
import pandas as pd
import [Link] as plt
sports = [Link]({'Sport': ['Football', 'Cricket', 'Cricket', 'Basketball', 'Tennis',
'Football', 'Tennis']})
counts = sports['Sport'].value_counts()
[Link](figsize=(6,6))
[Link](counts, labels=[Link], autopct='%1.1f%%', startangle=140)
[Link]('Student Sports Preferences')
[Link]()
Exercise 7: Exam Scores
import pandas as pd
import [Link] as plt
import numpy as np
scores = [Link]({'Math_Score': [Link](30, 100, 100)})
bins = [Link](0, 101, 10)
[Link](figsize=(8,5))
n, bins, patches = [Link](scores['Math_Score'], bins=bins, edgecolor='black')
for patch, bin_center in zip(patches, bins[:-1]):
if bin_center < 50:
patch.set_facecolor('red')
elif bin_center < 75:
patch.set_facecolor('yellow')
else:
patch.set_facecolor('green')
[Link]("Histogram of Math Scores")
[Link]("Score Range")
[Link]("Number of Students")
[Link]()
Exercise 1 — E-commerce customers + transactions
# exercise1_imsv1.py
import pandas as pd
import numpy as np
# === Config: filenames ===
customers_file = "[Link]" # Columns: CustomerID, Name, Age, Email, Region
transactions_file = "[Link]" # Columns: TransactionID, CustomerID, Product,
Quantity, Price
output_integrated = "Integrated_Customers.csv"
# === 1. Load datasets ===
customers = pd.read_csv(customers_file)
transactions = pd.read_csv(transactions_file)
print("Customers shape:", [Link])
print("Transactions shape:", [Link])
# === 2. Outer join on CustomerID to keep all customers even if no transactions ===
# We'll keep transactions' info where available; customers without transactions will have
NaNs for transaction columns.
integrated = [Link](customers, transactions, on="CustomerID", how="outer",
indicator=True, suffixes=("", "_txn"))
# === 3. Standardize Region values (example mapping) ===
region_map = {
"Bangalore": "Bengaluru",
"bangalore": "Bengaluru",
"Bengaluru": "Bengaluru",
"Delhi": "Delhi",
"delhi": "Delhi",
"New Delhi": "Delhi",
# Add other mappings as needed
}
# Normalize case then map
integrated['Region'] = integrated['Region'].astype(str).[Link]()
integrated['Region'] = integrated['Region'].replace(region_map)
# If you want to map with case-insensitivity for unknown variants:
integrated['Region'] = integrated['Region'].replace({[Link](): v for k, v in
region_map.items()})
# ensure title-case for readability
integrated['Region'] = integrated['Region'].replace('nan', [Link])
# === 4. Create TotalAmount = Quantity * Price ===
# Coerce numeric and fill missing quantity/price with 0 for calculation (but keep NaNs if
you prefer)
integrated['Quantity'] = pd.to_numeric(integrated['Quantity'], errors='coerce')
integrated['Price'] = pd.to_numeric(integrated['Price'], errors='coerce')
integrated['TotalAmount'] = integrated['Quantity'] * integrated['Price']
# For customers with no transactions, TotalAmount will be NaN; we can set to 0 if desired:
integrated['TotalAmount'] = integrated['TotalAmount'].fillna(0)
# === 5. Top 5 customers by total spending (by CustomerID) ===
# Sum total amount per customer (in case of multiple transactions)
spending_by_customer = [Link]('CustomerID', as_index=False)
['TotalAmount'].sum()
top5_by_spend = spending_by_customer.sort_values(by='TotalAmount',
ascending=False).head(5)
print("\nTop 5 customers by total spending (CustomerID, TotalAmount):")
print(top5_by_spend)
# === 6. Display only customers from "Delhi" region ===
customers_from_delhi = integrated[integrated['Region'].[Link]() == 'delhi']
print("\nCustomers from Delhi (sample):")
print(customers_from_delhi[['CustomerID', 'Name', 'Region']].drop_duplicates().head(10))
# === 7. Handle missing values
# Age: fill with mean age
if 'Age' in [Link]:
mean_age = pd.to_numeric(integrated['Age'], errors='coerce').mean()
integrated['Age'] = pd.to_numeric(integrated['Age'], errors='coerce')
integrated['Age'] = integrated['Age'].fillna(round(mean_age, 1))
print(f"\nFilled missing Age with mean: {mean_age:.2f}")
# Region: fill with mode
if 'Region' in [Link]:
mode_region = integrated['Region'].mode(dropna=True)
if not mode_region.empty:
mode_value = mode_region.iloc[0]
integrated['Region'] = integrated['Region'].fillna(mode_value)
print("Filled missing Region with mode:", mode_value)
else:
print("No mode found for Region (all NaN) — leaving as NaN.")
# === 8. Save final integrated dataset ===
integrated.to_csv(output_integrated, index=False)
print("\nSaved integrated dataset to:", output_integrated)
# === 9. Check number of customers present in Customers dataset but not in Transactions
dataset ===
# Customers dataset unique CustomerIDs:
customer_ids_customers = set(customers['CustomerID'].dropna().unique())
customer_ids_transactions = set(transactions['CustomerID'].dropna().unique())
only_in_customers = customer_ids_customers - customer_ids_transactions
print("\nNumber of customers in Customers but not in Transactions:",
len(only_in_customers))
print("Sample CustomerIDs only in customers:", list(only_in_customers)[:10])
# === 10. Top 5 customers who spent the most (again, tied to earlier step) ===
print("\nTop 5 customers who spent the most (detailed):")
top5_ids = top5_by_spend['CustomerID'].tolist()
# Join with customers info
top5_details = customers[customers['CustomerID'].isin(top5_ids)].merge(top5_by_spend,
on='CustomerID', how='right')
print(top5_details)
# End of Exercise 1
Exercise 2 — Generic dataset cleaning, merging, normalization, and plotting
Below I provide a flexible script that you can run for any Kaggle dataset you select (I’ll use
placeholders). I’ll assume you choose World Happiness Report as the primary dataset and
another dataset that contains a Country column (for example GDP by country) for
integration. Update filenames accordingly.
# exercise2_clean_merge_norm.py
import pandas as pd
import numpy as np
import [Link] as plt
# === Config: filenames - update to your downloaded files ===
primary_file = "world_happiness.csv" # e.g., columns: Country, Year, HappinessScore,
GDPPerCapita, ...
secondary_file = "gdp_by_country.csv" # e.g., columns: Country, Year, GDP
out_cleaned = "dataset_cleaned.csv"
out_integrated = "dataset_integrated.csv"
out_standardized = "dataset_standardized.csv"
# === 1. Load the primary dataset ===
df = pd.read_csv(primary_file)
print("Primary shape:", [Link])
print("\nColumn names and dtypes:")
print([Link])
print("\nFirst 10 records:")
print([Link](10))
print("\nSummary (numeric):")
print([Link](include=[[Link]]))
# === 2. Missing values count ===
print("\nMissing values per column:")
print([Link]().sum())
# === 3. Three methods to handle missing values ===
df_method_mean = [Link]()
df_method_median = [Link]()
df_method_ffill = [Link]()
df_method_drop = [Link]()
# Impute numeric columns with mean/median/mode (demonstration for numeric
columns)
num_cols = df.select_dtypes(include=[[Link]]).[Link]()
cat_cols = df.select_dtypes(include=['object', 'category']).[Link]()
# a) Mean imputation
for c in num_cols:
df_method_mean[c] = df_method_mean[c].fillna(df_method_mean[c].mean())
# b) Median imputation
for c in num_cols:
df_method_median[c] = df_method_median[c].fillna(df_method_median[c].median())
# c) Forward fill then backward fill
df_method_ffill[num_cols + cat_cols] = df_method_ffill[num_cols + cat_cols].ffill().bfill()
# d) Drop rows with NA
df_method_drop = df_method_drop.dropna()
print("\nShapes after different missing-value strategies:")
print("Mean impute:", df_method_mean.shape)
print("Median impute:", df_method_median.shape)
print("Ffill/bfill:", df_method_ffill.shape)
print("Drop NA:", df_method_drop.shape)
# === 4. Detect duplicates, remove them, report ===
duplicates = [Link]()
print("\nDuplicate count in original df:", [Link]())
df_no_dup = df.drop_duplicates()
print("Shape after removing duplicates:", df_no_dup.shape)
# === 5. Standardize categorical inconsistencies (example) ===
# Example mapping: 'IND' -> 'India', 'india' -> 'India'
# Create a function for flexible mapping
def standardize_country(col):
mapping = {
"IND": "India",
"india": "India",
"United States": "United States",
"USA": "United States",
"U.S.A.": "United States",
# extend as needed
}
return [Link](mapping)
if 'Country' in df_no_dup.columns:
df_no_dup['Country'] = df_no_dup['Country'].astype(str).[Link]()
df_no_dup['Country'] = standardize_country(df_no_dup['Country'])
# === 6. Clean text columns (strip, lowercase) ===
for c in df_no_dup.select_dtypes(include=['object']).columns:
df_no_dup[c] = df_no_dup[c].astype(str).[Link]()
# Example: lower-case all text
df_no_dup[c] = df_no_dup[c].[Link](r'\s+', ' ', regex=True) # collapse spaces
# Optionally lower-case:
# df_no_dup[c] = df_no_dup[c].[Link]()
# === 7. Outlier detection using IQR for one numeric column (example: 'HappinessScore' or
first numeric) ===
num_example = num_cols[0] if num_cols else None
if num_example:
Q1 = df_no_dup[num_example].quantile(0.25)
Q3 = df_no_dup[num_example].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
before_rows = df_no_dup.shape[0]
df_no_outliers = df_no_dup[(df_no_dup[num_example] >= lower) &
(df_no_dup[num_example] <= upper)]
after_rows = df_no_outliers.shape[0]
print(f"\nOutlier removal on {num_example}: removed {before_rows - after_rows} rows")
else:
df_no_outliers = df_no_dup
# Save cleaned dataset
df_no_outliers.to_csv(out_cleaned, index=False)
print("\nSaved cleaned dataset to:", out_cleaned)
# === 8. Integration with a second dataset (by Country or other key) ===
other = pd.read_csv(secondary_file)
print("\nSecondary shape:", [Link])
# Identify common key(s)
common_cols = set(df_no_outliers.columns).intersection(set([Link]))
print("Common columns for join:", common_cols)
# Suppose key is 'Country' and 'Year' if both exist
join_keys = []
if 'Country' in common_cols:
join_keys.append('Country')
if 'Year' in common_cols:
join_keys.append('Year')
if not join_keys:
print("No natural join keys found. You may need to align columns manually.")
else:
print("Using join keys:", join_keys)
inner = [Link](df_no_outliers, other, on=join_keys, how='inner')
left = [Link](df_no_outliers, other, on=join_keys, how='left')
right = [Link](df_no_outliers, other, on=join_keys, how='right')
outer = [Link](df_no_outliers, other, on=join_keys, how='outer')
print("\nShapes after joins:")
print("Inner:", [Link])
print("Left:", [Link])
print("Right:", [Link])
print("Outer:", [Link])
# Save integrated dataset (choose the one you need)
outer.to_csv(out_integrated, index=False)
print("Saved integrated dataset to:", out_integrated)
# Example: add GDP column into primary if available in other
if 'GDP' in [Link]:
merged_with_gdp = [Link]()
# If column mismatches: rename before merging
merged_with_gdp['GDP'] = merged_with_gdp.get('GDP', [Link])
print("Merged dataset with GDP (sample):")
print(merged_with_gdp[['Country', 'GDP']].dropna().head())
# === 9. Concatenate row-wise and column-wise ===
# Row-wise (append) - both datasets need same columns
try:
concat_rows = [Link]([df_no_outliers, other], axis=0, ignore_index=True, sort=False)
print("\nConcat row-wise shape:", concat_rows.shape)
except Exception as e:
print("Row-wise concat failed (probably different columns). Error:", e)
# Column-wise
concat_cols = [Link]([df_no_outliers.reset_index(drop=True),
other.reset_index(drop=True)], axis=1)
print("Concat column-wise shape:", concat_cols.shape)
# Save standardized/integrated datasets later (below)
# === 10. Numerical analysis and normalizations ===
# Choose two numerical columns for demonstration: pick first two numeric columns
two_nums = num_cols[:2] if len(num_cols) >= 2 else num_cols
print("\nSelected numeric columns for normalization:", two_nums)
if two_nums:
sample_df = df_no_outliers[two_nums].dropna()
# Summary statistics
print("\nSummary statistics for selected columns:")
print(sample_df.agg(['mean', 'median', 'std', 'min', 'max']))
# Min-Max normalization
mm = sample_df.copy()
for c in two_nums:
mm[c + "_minmax"] = (mm[c] - mm[c].min()) / (mm[c].max() - mm[c].min())
# Z-score
zs = sample_df.copy()
for c in two_nums:
zs[c + "_zscore"] = (zs[c] - zs[c].mean()) / zs[c].std(ddof=0)
# Decimal scaling
ds = sample_df.copy()
for c in two_nums:
max_abs = [Link](ds[c]).max()
j = int([Link](np.log10(max_abs + 1)))
ds[c + "_decscale"] = ds[c] / (10 ** j)
# Verify min/max for Min-Max
for c in two_nums:
col = c + "_minmax"
print(f"{col} min, max: {mm[col].min():.6f}, {mm[col].max():.6f}")
# Verify z-score mean/std
for c in two_nums:
col = c + "_zscore"
print(f"{col} mean ~ {zs[col].mean():.6f}, std ~ {zs[col].std(ddof=0):.6f}")
# Build sample comparison table (first 5 rows)
compare = [Link]([sample_df.head(5).reset_index(drop=True),
mm[[c + "_minmax" for c in two_nums]].head(5).reset_index(drop=True),
zs[[c + "_zscore" for c in two_nums]].head(5).reset_index(drop=True),
ds[[c + "_decscale" for c in two_nums]].head(5).reset_index(drop=True)
], axis=1)
print("\nComparison sample (original, minmax, zscore, decscale):")
print(compare)
# Save standardized dataset (example: add normalized columns to df_no_outliers)
df_std = df_no_outliers.copy()
for c in two_nums:
df_std[c + "_minmax"] = (df_std[c] - df_std[c].min()) / (df_std[c].max() - df_std[c].min())
df_std[c + "_zscore"] = (df_std[c] - df_std[c].mean()) / df_std[c].std(ddof=0)
df_std.to_csv(out_standardized, index=False)
print("\nSaved standardized dataset to:", out_standardized)
# Plot histograms
feature = two_nums[0]
[Link](figsize=(12, 8))
[Link](3, 1, 1)
[Link](df_no_outliers[feature].dropna(), bins=30)
[Link](f"{feature} - Original")
[Link](3, 1, 2)
[Link](df_std[feature + "_minmax"].dropna(), bins=30)
[Link](f"{feature} - MinMax scaled")
[Link](3, 1, 3)
[Link](df_std[feature + "_zscore"].dropna(), bins=30)
[Link](f"{feature} - Z-score scaled")
plt.tight_layout()
[Link]()
else:
print("Not enough numeric columns to demonstrate normalization.")
# Save final integrated dataset if not saved
# out_integrated already saved earlier (outer)
Exercise 3 — Pima Indians Diabetes dataset (clean, integrate, standardize)
# exercise3_pima_processing.py
import pandas as pd
import numpy as np
import [Link] as plt
# === Config ===
pima_file = "[Link]" # Kaggle Pima dataset; typical columns: Pregnancies, Glucose,
BloodPressure, SkinThickness, Insulin, BMI, DiabetesPedigreeFunction, Age, Outcome
out_cleaned = "diabetes_cleaned.csv"
demographics_file = "[Link]"
medical_file = "[Link]"
integrated_file = "diabetes_integrated.csv"
std_file = "diabetes_standardized.csv"
# === 1. Load dataset ===
df = pd.read_csv(pima_file)
print("Shape:", [Link])
print("Columns:", [Link]())
print("\nFirst 10 records:")
print([Link](10))
print("\nSummary statistics:")
print([Link]())
# === 2. Identify missing/invalid values ===
# In this dataset, zeros for Glucose, BloodPressure, SkinThickness, Insulin, BMI are
considered invalid
invalid_cols = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
for c in invalid_cols:
if c in [Link]:
n_invalid = (df[c] == 0).sum()
print(f"Invalid (0) count in {c}: {n_invalid}")
# Replace zeros with NaN for these columns to treat as missing
df[invalid_cols] = df[invalid_cols].replace(0, [Link])
# === 3. Two methods to handle missing values: mean imputation vs drop rows ===
df_mean_impute = [Link]()
for c in invalid_cols:
if c in df_mean_impute.columns:
df_mean_impute[c] = df_mean_impute[c].fillna(df_mean_impute[c].mean())
df_drop = [Link]()
print("\nAfter mean imputation shape:", df_mean_impute.shape)
print("After dropping rows with missing values shape:", df_drop.shape)
# Save cleaned dataset using mean imputation choice
df_mean_impute.to_csv(out_cleaned, index=False)
print("\nSaved cleaned dataset (mean imputed) to:", out_cleaned)
# === 4. Detect and remove duplicate rows ===
dupes = df_mean_impute.duplicated().sum()
print("\nDuplicates found:", dupes)
df_mean_impute = df_mean_impute.drop_duplicates()
print("Shape after removing duplicates:", df_mean_impute.shape)
# === 5. Detect outliers using IQR on Glucose (example) ===
col = 'Glucose'
if col in df_mean_impute.columns:
Q1 = df_mean_impute[col].quantile(0.25)
Q3 = df_mean_impute[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
before = df_mean_impute.shape[0]
df_no_outliers = df_mean_impute[(df_mean_impute[col] >= lower) &
(df_mean_impute[col] <= upper)]
after = df_no_outliers.shape[0]
print(f"Removed {before - after} outliers from {col}")
else:
df_no_outliers = df_mean_impute
# Save cleaned dataset
df_no_outliers.to_csv(out_cleaned, index=False)
print("Saved cleaned dataset (no outliers) to:", out_cleaned)
# === 6. Split into [Link] and [Link] ===
demographics = df_no_outliers[['Pregnancies', 'Age', 'Outcome']].copy()
medical = df_no_outliers[['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI',
'DiabetesPedigreeFunction', 'Outcome']].copy()
demographics.to_csv(demographics_file, index=False)
medical.to_csv(medical_file, index=False)
print("\nSaved demographics and medical CSVs:", demographics_file, medical_file)
print("Demographics shape:", [Link])
print("Medical shape:", [Link])
# === 7. Merge the two CSVs on Outcome (example) and compare join types ===
dem = pd.read_csv(demographics_file)
med = pd.read_csv(medical_file)
inner = [Link](dem, med, on='Outcome', how='inner', suffixes=('_dem', '_med'))
left = [Link](dem, med, on='Outcome', how='left', suffixes=('_dem', '_med'))
right = [Link](dem, med, on='Outcome', how='right', suffixes=('_dem', '_med'))
outer = [Link](dem, med, on='Outcome', how='outer', suffixes=('_dem', '_med'))
print("\nMerge shapes:")
print("Inner:", [Link])
print("Left:", [Link])
print("Right:", [Link])
print("Outer:", [Link])
# Save integrated data (choose outer here)
outer.to_csv(integrated_file, index=False)
print("Saved integrated dataset to:", integrated_file)
# === 8. Concatenate row-wise and column-wise ===
concat_rows = [Link]([dem, med], axis=0, ignore_index=True, sort=False)
concat_cols = [Link]([dem.reset_index(drop=True), med.reset_index(drop=True)],
axis=1)
print("\nConcat row-wise shape:", concat_rows.shape)
print("Concat column-wise shape:", concat_cols.shape)
# === 9. Standardization: choose Glucose and BMI ===
cols_to_scale = ['Glucose', 'BMI']
stats = df_no_outliers[cols_to_scale].agg(['mean', 'median', 'std', 'min', 'max'])
print("\nDescriptive stats for selected cols:")
print(stats)
# Min-Max
df_std = df_no_outliers.copy()
for c in cols_to_scale:
df_std[c + "_minmax"] = (df_std[c] - df_std[c].min()) / (df_std[c].max() - df_std[c].min())
# Z-score
for c in cols_to_scale:
df_std[c + "_zscore"] = (df_std[c] - df_std[c].mean()) / df_std[c].std(ddof=0)
# Decimal scaling
for c in cols_to_scale:
max_abs = [Link](df_std[c]).max()
j = int([Link](np.log10(max_abs + 1)))
df_std[c + "_decscale"] = df_std[c] / (10 ** j)
# Verify
for c in cols_to_scale:
print(f"\n{c} minmax min/max: {df_std[c + '_minmax'].min():.6f}/{df_std[c +
'_minmax'].max():.6f}")
print(f"{c} zscore mean/std: {df_std[c + '_zscore'].mean():.6f}/{df_std[c +
'_zscore'].std(ddof=0):.6f}")
# Small sample comparison
print("\nSample comparison:")
print(df_std[[*cols_to_scale, *(c + "_minmax" for c in cols_to_scale), *(c + "_zscore" for c in
cols_to_scale)]].head(10))
# Histograms: Glucose before and after
[Link](figsize=(12, 8))
[Link](3, 1, 1)
[Link](df_no_outliers['Glucose'].dropna(), bins=30)
[Link]('Glucose - Original')
[Link](3, 1, 2)
[Link](df_std['Glucose_minmax'].dropna(), bins=30)
[Link]('Glucose - MinMax')
[Link](3, 1, 3)
[Link](df_std['Glucose_zscore'].dropna(), bins=30)
[Link]('Glucose - Z-score')
plt.tight_layout()
[Link]()
# Save standardized dataset
df_std.to_csv(std_file, index=False)
print("\nSaved standardized dataset to:", std_file)