|
| 1 | +import csv |
| 2 | +import os |
| 3 | +import pandas as pd |
| 4 | +import matplotlib.pyplot as plt |
| 5 | +from datetime import datetime |
| 6 | +from matplotlib.dates import DateFormatter |
| 7 | + |
| 8 | +# ---------- CLASSES ---------- |
| 9 | + |
| 10 | +class LogEntry: |
| 11 | + def __init__(self, date): |
| 12 | + self.date = date |
| 13 | + |
| 14 | +class Workout(LogEntry): |
| 15 | + def __init__(self, date, workout_type, duration, calories): |
| 16 | + super().__init__(date) |
| 17 | + self.workout_type = workout_type |
| 18 | + self.duration = duration |
| 19 | + self.calories = calories |
| 20 | + |
| 21 | +class Meal(LogEntry): |
| 22 | + def __init__(self, date, meal_type, food, calories): |
| 23 | + super().__init__(date) |
| 24 | + self.meal_type = meal_type |
| 25 | + self.food = food |
| 26 | + self.calories = calories |
| 27 | + |
| 28 | +# ---------- FILE PATHS ---------- |
| 29 | + |
| 30 | +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') |
| 31 | +WORKOUTS_FILE = os.path.join(DATA_DIR, 'workouts.csv') |
| 32 | +MEALS_FILE = os.path.join(DATA_DIR, 'meals.csv') |
| 33 | + |
| 34 | +# ---------- READ CSV MANUALLY ---------- |
| 35 | + |
| 36 | +def read_workouts_manual(file_path): |
| 37 | + workouts = [] |
| 38 | + with open(file_path, newline='') as csvfile: |
| 39 | + reader = csv.reader(csvfile) |
| 40 | + next(reader) # Skip header |
| 41 | + for row in reader: |
| 42 | + date = row[0].strip() |
| 43 | + workout_type = row[1].strip() |
| 44 | + duration = int(row[2].strip()) |
| 45 | + calories = int(row[3].strip()) |
| 46 | + workouts.append(Workout(date, workout_type, duration, calories)) |
| 47 | + return workouts |
| 48 | + |
| 49 | +# ---------- USING PANDAS ---------- |
| 50 | + |
| 51 | +def load_and_clean_data(): |
| 52 | + df_workouts = pd.read_csv(WORKOUTS_FILE) |
| 53 | + df_meals = pd.read_csv(MEALS_FILE) |
| 54 | + |
| 55 | + df_workouts['date'] = pd.to_datetime(df_workouts['date']) |
| 56 | + df_meals['date'] = pd.to_datetime(df_meals['date']) |
| 57 | + |
| 58 | + # Fill any missing values in workout data with zeros (e.g., missing durations or calories) |
| 59 | + df_workouts.fillna(0, inplace=True) |
| 60 | + # Fill any missing values in meal data with "Unknown" (e.g., missing food descriptions) |
| 61 | + df_meals.fillna("Unknown", inplace=True) |
| 62 | + |
| 63 | + return df_workouts, df_meals |
| 64 | + |
| 65 | +# ---------- ANALYSIS ---------- |
| 66 | + |
| 67 | +def summarize_data(df_workouts, df_meals): |
| 68 | + workout_summary = df_workouts.groupby('date')['calories_burned'].sum().reset_index() |
| 69 | + meal_summary = df_meals.groupby('date')['calories'].sum().reset_index() |
| 70 | + |
| 71 | + combined = pd.merge(workout_summary, meal_summary, on='date', how='outer').fillna(0) |
| 72 | + combined['net_calories'] = combined['calories'] - combined['calories_burned'] |
| 73 | + return combined |
| 74 | + |
| 75 | +# ---------- VISUALIZATION ---------- |
| 76 | + |
| 77 | +def plot_fitness_trends(combined_df): |
| 78 | + # Create a new figure with specified size (width: 16 inches, height: 10 inches) |
| 79 | + # This creates a larger plot that is easier to read and analyze |
| 80 | + plt.figure(figsize=(16, 10)) |
| 81 | + |
| 82 | + # Plot calories consumed with circular markers |
| 83 | + plt.plot(combined_df['date'], combined_df['calories'], label="Calories Consumed", marker='o') |
| 84 | + |
| 85 | + # Plot calories burned with x markers for visual distinction |
| 86 | + plt.plot(combined_df['date'], combined_df['calories_burned'], label="Calories Burned", marker='x') |
| 87 | + |
| 88 | + # Plot net calories (consumed - burned) with dashed line style |
| 89 | + # This shows the caloric balance for each day |
| 90 | + plt.plot(combined_df['date'], combined_df['net_calories'], label="Net Calories", linestyle='--') |
| 91 | + |
| 92 | + # Calculate and plot a 2-day rolling average of net calories |
| 93 | + # This smooths out daily fluctuations and shows the overall trend |
| 94 | + rolling = combined_df['net_calories'].rolling(window=2).mean() |
| 95 | + plt.plot(combined_df['date'], rolling, label="Rolling Mean (Net)", linestyle='dotted') |
| 96 | + |
| 97 | + # Add axis labels with increased font size for better readability |
| 98 | + plt.xlabel('Date', fontsize=14) |
| 99 | + plt.ylabel('Calories', fontsize=14) |
| 100 | + |
| 101 | + # Format the x-axis to display dates in YYYY-MM-DD format |
| 102 | + # This ensures consistent date representation on the chart |
| 103 | + date_format = DateFormatter('%Y-%m-%d') |
| 104 | + plt.gca().xaxis.set_major_formatter(date_format) |
| 105 | + |
| 106 | + # Rotate x-axis labels by 45 degrees to prevent overlap and increase font size |
| 107 | + plt.xticks(rotation=45, fontsize=12) |
| 108 | + plt.yticks(fontsize=12) |
| 109 | + |
| 110 | + plt.title('Fitness Tracker Summary', fontsize=16) # Add a descriptive title to the chart with larger font |
| 111 | + |
| 112 | + plt.legend() # Add a legend to identify each line in the plot |
| 113 | + |
| 114 | + plt.grid(True) # Add a grid to make it easier to read values from the chart |
| 115 | + |
| 116 | + plt.tight_layout() # Adjust layout to ensure all elements fit without overlapping |
| 117 | + |
| 118 | + plt.show() # Display the completed chart |
| 119 | + |
| 120 | + |
| 121 | +# ---------- MAIN FUNCTION ---------- |
| 122 | + |
| 123 | +def main(): |
| 124 | + print("Loading data...") |
| 125 | + df_workouts, df_meals = load_and_clean_data() |
| 126 | + |
| 127 | + print("\nSummarizing data...") |
| 128 | + combined = summarize_data(df_workouts, df_meals) |
| 129 | + print(combined) |
| 130 | + |
| 131 | + print("\nPlotting results...") |
| 132 | + plot_fitness_trends(combined) |
| 133 | + |
| 134 | +if __name__ == "__main__": |
| 135 | + main() |
0 commit comments