diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..7b7b847 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,19 +19,27 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" # TODO: 你的程式碼 - pass + arr = (np.arange(5) + 1) * 10 + return arr.mean().item() + +# print(green_mean()) def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" # TODO: 你的程式碼 - pass + arr = (np.arange(5) + 1) * 10 * 2 + return arr +# print(green_double()) def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" # TODO: 你的程式碼 - pass + arr = (np.arange(5) + 1) * 10 + return arr[arr>25] + +# print(green_filter()) # ============================================================ @@ -42,7 +50,7 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 - pass + return int((prices > 1000).sum()) def yellow_top3_stock_indices(stocks): @@ -51,7 +59,7 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 - pass + return np.argsort(-stocks)[:3] def yellow_restock_cost(prices, stocks): @@ -60,8 +68,18 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 - pass - + mask = prices<500 + return sum(50 * prices[mask]) + +# import pandas as pd +# C:\python_data\python-da-homework-2026\datasets\ecommerce\products.csv +# df = pd.read_csv('../datasets/ecommerce/products.csv') +# print(df['unit_price']) +# print(df['stock_qty']) +# print(yellow_expensive_count(df['unit_price'])) +# print(type(yellow_expensive_count(df['unit_price']))) +# print(yellow_top3_stock_indices(df['stock_qty'])) +# print(yellow_restock_cost(df['unit_price'], df['stock_qty'])) # ============================================================ # 🔴 挑戰題(25 分) @@ -77,4 +95,11 @@ def red_double11_prices(prices, stocks): 提示:np.where 可以巢狀使用 """ # TODO: 你的程式碼 - pass + # print(prices, stocks) + new_prices = np.where(stocks>=100, prices*0.7, np.where(stocks>=20, prices*0.9, prices)) + # print(type(new_prices)) + return new_prices + +# import pandas as pd +# df = pd.read_csv('../datasets/ecommerce/products.csv') +# print(red_double11_prices(df['unit_price'], df['stock_qty'])) \ No newline at end of file diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..f676c3f 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -20,7 +20,9 @@ def green_read_csv(): 提示:pd.read_csv() """ # TODO: 你的程式碼 - pass + df = pd.read_csv('datasets/ecommerce/orders_raw.csv') + # print(df.head()) + return df def green_shape(df): @@ -29,7 +31,7 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - pass + return df.shape def green_dtypes(df): @@ -38,8 +40,12 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - pass + return df.dtypes +# temp = green_read_csv() +# print(green_read_csv()) +# print(green_shape(temp)) +# print(green_dtypes(temp)) # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) @@ -52,7 +58,9 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 - pass + df2 = df.copy() + df2.columns = df2.columns.str.strip().str.lower() + return df2 def yellow_clean_amount(df): @@ -63,7 +71,9 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + df2 = df.copy() + df2['amount'] = df2['amount'].str.replace('$', '').str.replace(',', '').astype(float) + return df2 def yellow_drop_duplicates(df): @@ -72,9 +82,12 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass - + df2 = df.drop_duplicates() + return df2 +# print(yellow_clean_columns(temp)) +# print(yellow_clean_amount(temp)) +# print(yellow_drop_duplicates(temp)) # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ @@ -93,4 +106,15 @@ def red_clean_orders(path): 提示:pd.to_datetime(errors='coerce') """ # TODO: 你的程式碼 - pass + df = pd.read_csv(path) + df.columns = df.columns.str.strip().str.lower() + df['amount'] = df['amount'].str.replace('$', '').str.replace(',', '').astype(float) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['order_date', 'amount']) + df = df.drop_duplicates() + + return df + +# print(red_clean_orders('../datasets/ecommerce/orders_raw.csv').head()) + +# print(red_clean_orders('../datasets/ecommerce/orders_raw.csv').shape) diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..7a264fe 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,21 +24,41 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass + df1 = pd.read_csv('datasets/ecommerce/orders_clean.csv') + df2 = pd.read_csv('datasets/ecommerce/customers.csv') + df3 = pd.read_csv('datasets/ecommerce/products.csv') + + df = ( + df1 + .merge(df2, on='customer_id', how='left') + .merge(df3, on='product_id', how='left') + ) + + # print(df.head()) + # print(df.shape) + # print(df.columns) + + return df def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 - pass + # print(len(df)) + return len(df) def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 - pass + return list(df.columns) +# temp = green_load_and_merge() +# leng = green_row_count(temp) +# leng = green_column_list(temp) +# print(leng) + # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ @@ -50,7 +70,9 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + product = df.groupby('category')['amount'].sum().idxmax() + # print(product) + return product def yellow_gold_vip_stats(df): @@ -60,7 +82,9 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + order_nums = df[df['vip_level'] == 'Gold']['order_id'].count() + order_sum = df[df['vip_level'] == 'Gold']['amount'].sum() + return (order_nums, order_sum) def yellow_region_avg_amount(df): @@ -70,7 +94,15 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + avg_money = df.groupby('region')['amount'].mean() + # print(avg_money) + # print(type(avg_money)) + return avg_money + +# temp = green_load_and_merge() +# tmp = yellow_top_category(temp) +# tmp = yellow_gold_vip_stats(temp) +# tmp = yellow_region_avg_amount(temp) # ============================================================ @@ -94,4 +126,21 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + rfm = df.groupby('customer_id').agg( + R = ('order_date', 'max'), + F = ('order_id', 'count'), + M = ('amount', 'sum') + ) + + cus_name = df[['customer_id', 'customer_name']].drop_duplicates() + + rfm = rfm.merge( + cus_name, + on= 'customer_id', + how='right' + ).sort_values('M', ascending=False) + rfm = rfm.head(5).reset_index()[['customer_id', 'customer_name', 'R', 'F', 'M']] + # print(rfm) + return rfm + +# tmp = red_rfm_top5(temp) \ No newline at end of file diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..8a7fe88 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -27,8 +27,9 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass - + df = _load_data() + ans = df.groupby(df['order_date'].dt.month)['amount'].mean() + return ans def green_top3_dates(): """ @@ -37,7 +38,9 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = _load_data() + ans = df['order_date'].dt.date.value_counts().head(3) + return ans def green_date_range(): @@ -46,7 +49,10 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - pass + df = _load_data() + # temp = (df['order_date'].min(), df['order_date'].max()) + # print(type(temp)) + return (df['order_date'].min(), df['order_date'].max()) # ============================================================ @@ -60,7 +66,8 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 - pass + df = _load_data() + return df.set_index('order_date').resample('ME')['amount'].sum() def yellow_rolling_avg(monthly_revenue): @@ -71,7 +78,7 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 - pass + return monthly_revenue.rolling(window=3).mean() def yellow_category_median(df): @@ -81,8 +88,16 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + return df.groupby('category')['amount'].median().sort_values(ascending=False) +# tmp = _load_data() +# tmp = green_avg_by_month() +# tmp = green_top3_dates() +# tmp = green_date_range() +# tmp = yellow_monthly_revenue() +# tmp = yellow_rolling_avg(tmp) +# tmp = yellow_category_median(_load_data()) +# print(tmp) # ============================================================ # 🔴 挑戰題(25 分) @@ -101,4 +116,15 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = _load_data() + ans = df.groupby(df['order_date'].dt.to_period('M')).agg( + order_count= ('order_id', 'count'), + revenue= ('amount', 'sum'), + active_customers= ('customer_id', 'nunique'), + ).sort_index() + + ans['avg_order_value'] = ans['revenue'] / ans['order_count'] + ans['revenue_growth'] = ans['revenue'].pct_change() * 100 + return ans + +# print(red_monthly_report()) \ No newline at end of file diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..f0e5888 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -5,6 +5,8 @@ 資料路徑:datasets/ecommerce/orders_enriched.csv """ +from itertools import groupby + import matplotlib matplotlib.use("Agg") # 無 GUI 環境也能跑 import matplotlib.pyplot as plt @@ -29,7 +31,17 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 4)) + + sns.countplot(data=df, x='category', color='steelblue') + plt.title('Order Count by Category', fontweight='bold') + plt.xlabel('Category') + plt.ylabel('Order Count') + plt.tight_layout() + # plt.show() + fig = plt.gcf() + return fig def green_hist_amount(): @@ -39,8 +51,14 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass - + df = _load_data() + plt.figure(figsize=(8, 4)) + sns.histplot(data= df, x= 'amount', kde=True, bins=20) + plt.xlabel('amount') + plt.tight_layout() + # plt.show() + fig = plt.gcf() + return fig def green_set_labels(): """ @@ -51,7 +69,17 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 4)) + + sns.countplot(data=df, x='category', color='steelblue') + plt.title('Order Count by Category', fontweight='bold') + plt.xlabel('Category') + plt.ylabel('Order Count') + plt.tight_layout() + # plt.show() + fig = plt.gcf() + return fig # ============================================================ @@ -68,7 +96,22 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = _load_data() + df = df[df['region'].isin(['North', 'South'])] + df['month'] = df['order_date'].dt.month + df = df.groupby(['region', 'month'])['amount'].sum().reset_index() + # print(df) + plt.figure(figsize=(8, 4)) + sns.lineplot(data=df, x='month', y='amount', hue='region', marker='o', linewidth=2) + plt.title('Monthly Revenue: North vs South', fontweight='bold') + plt.xlabel('Month') + plt.ylabel('Amount') + plt.legend(title='Region', loc='upper right') + plt.tight_layout() + # plt.show() + + fig = plt.gcf() + return fig def yellow_box_vip(): @@ -78,8 +121,18 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass - + df = _load_data() + # print(df.head()) + # df = df.groupby('vip_level')['amount'].reset_index() + plt.figure(figsize=(8, 4)) + sns.boxplot(x='vip_level', y='amount', data=df) + plt.title('VIP Level Distribution', fontweight='bold') + plt.xlabel('VIP Level') + plt.ylabel('Amount') + # plt.tight_layout() + # plt.show() + fig = plt.gcf() + return fig def yellow_scatter_price_amount(): """ @@ -88,8 +141,26 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass - + df = _load_data() + # print(df.head()) + plt.figure(figsize=(8, 4)) + sns.scatterplot(data=df, x='unit_price', y='amount') + plt.title('Price Amount by Unit', fontweight='bold') + plt.xlabel('Unit') + plt.ylabel('Amount') + plt.tight_layout() + # plt.show() + fig = plt.gcf() + return fig + +# tmp = _load_data() +# tmp = green_bar_category() +# tmp = green_hist_amount() +# tmp = green_set_labels() +# tmp = yellow_line_region_trend() +# tmp = yellow_box_vip() +# tmp = yellow_scatter_price_amount() +# print(tmp) # ============================================================ # 🔴 挑戰題(25 分) @@ -107,4 +178,38 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass + df = _load_data() + df['month'] = df['order_date'].dt.month + df = df[df['category'] == category] + # print(df.head()) + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + + df1 = df.groupby('month')['amount'].sum().reset_index() + sns.lineplot(data=df1, x='month', y='amount', ax=axes[0, 0]) + axes[0, 0].set_title(f'Monthly Revenue: {category}', fontweight='bold') + axes[0, 0].set_xlabel('Month') + axes[0, 0].set_ylabel('Amount') + + df2 = df.groupby('region')['amount'].sum().reset_index() + sns.barplot(data=df2, x='region', y='amount', color='steelblue', ax= axes[0, 1]) + axes[0, 1].set_title('Amount by Region', fontweight='bold') + axes[0, 1].set_xlabel('Region') + axes[0, 1].set_ylabel('Amount') + + df3 = df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head(5).reset_index() + sns.barplot(data=df3, y='product_name', x='amount', color='steelblue',ax= axes[1, 0]) + axes[1, 0].set_title(f'Top 5 Amount by Product in {category}', fontweight='bold') + axes[1, 0].set_xlabel('Amount') + axes[1, 0].set_ylabel('Product') + + # print(df['amount']) + sns.histplot(data=df['amount'], bins=30, ax=axes[1, 1]) + axes[1, 1].set_title(f'Money Distribution by {category}', fontweight='bold') + axes[1, 1].set_xlabel('Product') + axes[1, 1].set_ylabel('Amount') + + fig.tight_layout() + # plt.show() + return fig + +# red_category_dashboard() \ No newline at end of file diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..c57fcc9 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,7 +26,12 @@ def green_plotly_bar(): 提示:px.bar() """ # TODO: 你的程式碼 - pass + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') + # print(df.head()) + df = df.groupby('category')['amount'].sum().reset_index() + fig = px.bar(df, x='category', y='amount') + # fig.show() + return fig def green_plotly_line(): @@ -37,7 +42,14 @@ def green_plotly_line(): 提示:先 groupby 月份算總營收,再 px.line() """ # TODO: 你的程式碼 - pass + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') + df['order_date'] = pd.to_datetime(df['order_date']) + df['month'] = df['order_date'].dt.to_period('M').astype(str) + df = df.groupby('month')['amount'].sum().reset_index() + fig = px.line(df, x='month', y='amount', markers=True, title='Monthly Revenue') + fig.update_layout(height=400) + # fig.show() + return fig def green_plotly_pie(): @@ -48,8 +60,21 @@ def green_plotly_pie(): 提示:px.pie() """ # TODO: 你的程式碼 - pass - + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') + # df = df['vip_level'].value_counts().reset_index() + df = df.groupby('vip_level')['order_id'].count().reset_index() + print(df) + fig = px.pie(df, names='vip_level', values='order_id', + title='Number of Orders by VIP Level', hole=0.4) + fig.update_layout(height=400) + # fig.show() + # fig.write_html('first_figure.html', auto_open=True) + return fig + + +# green_plotly_bar() +# green_plotly_line() +# green_plotly_pie() # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) @@ -63,7 +88,30 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 回傳:合併後的 DataFrame """ # TODO: 你的程式碼 - pass + df = pd.read_csv(raw_path) + df_customers = pd.read_csv(customers_path) + df_products = pd.read_csv(products_path) + + df.columns = df.columns.str.strip().str.lower() + df['amount'] = df['amount'].str.replace('$', '').str.replace(',', '').astype(float) + df['order_date'] = pd.to_datetime(df['order_date']) + df = df.dropna(subset=['amount', 'order_date']) + df = df.drop_duplicates() + + df = ( + df + .merge(df_customers, how='left', on='customer_id') + .merge(df_products, how='left', on='product_id') + ) + + df['qty'] = df['qty'].fillna(df['amount']/df['unit_price']).astype(float) + # print(df.head()) + # print(df.info()) + # print(df_customers.head()) + # print(df_products.head()) + # print(df_customers.info()) + # print(df_products.info()) + return df def yellow_kpi_summary(df): @@ -77,7 +125,14 @@ def yellow_kpi_summary(df): } """ # TODO: 你的程式碼 - pass + kpi = {} + kpi['total_revenue'] = df['amount'].sum().item() + kpi['order_count'] = df['order_id'].count().item() + kpi['active_customers'] = df['customer_id'].nunique() + kpi['avg_order_value'] = df['amount'].mean().item() + + # print(kpi) + return kpi def yellow_plotly_scatter(df): @@ -91,8 +146,30 @@ def yellow_plotly_scatter(df): 提示:px.scatter(hover_data=['product_name']) """ # TODO: 你的程式碼 - pass - + item = df[['product_name', 'unit_price', 'amount', 'category']] + # print(item) + fig = px.scatter( + item, + x='unit_price', + y='amount', + color='category', + hover_name='product_name', + hover_data={ + 'unit_price': False, + 'amount': False, + 'category': False + } + ) + + fig.update_layout(height=400) + # fig.write_html('first_figure.html', auto_open=True) + return fig + + + +# temp = yellow_clean_and_merge('../datasets/ecommerce/orders_raw.csv', '../datasets/ecommerce/customers.csv', '../datasets/ecommerce/products.csv') +# tmp = yellow_kpi_summary(temp) +# tmp = yellow_plotly_scatter(temp) # ============================================================ # 🔴 挑戰題(25 分) @@ -115,4 +192,49 @@ def red_dashboard(): 提示:from plotly.subplots import make_subplots """ # TODO: 你的程式碼 - pass + df = yellow_clean_and_merge('datasets/ecommerce/orders_raw.csv', + 'datasets/ecommerce/customers.csv', + 'datasets/ecommerce/products.csv') + + + df['month'] = df['order_date'].dt.to_period('M').astype(str) + df1 = df.groupby('month')['amount'].sum().reset_index() + fig1 = px.line(df1, x='month', y='amount', markers=True, title='Monthly Revenue Trend') + + df2 = df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head(10).reset_index() + fig2 = px.bar(df2, x='product_name', y='amount', title='Top 10 Revenue of Products') + + df3 = df.groupby('region')['amount'].sum().reset_index() + fig3 = px.bar(df3, x='region', y='amount', title='Revenue of Regions') + + df4 = df.groupby('category')['amount'].sum().reset_index() + df4['percentage'] = df4['amount'] / df4['amount'].sum() * 100 + fig4 = px.pie( + df4, + names='category', + values='percentage', + title='Percentage of Revenue by Category' + ) + + + fig = make_subplots(rows=2, cols=2, + specs=[ + [{'type': 'xy'}, {'type': 'xy'}], + [{'type': 'xy'}, {'type': 'domain'}] + ], + subplot_titles=( + 'Monthly Revenue Trend', + 'Top 10 Revenue of Products', + 'Revenue of Regions', + 'Percentage of Revenue by Category' + )) + fig.add_trace(fig1.data[0], row=1, col=1) + fig.add_trace(fig2.data[0], row=1, col=2) + fig.add_trace(fig3.data[0], row=2, col=1) + fig.add_trace(fig4.data[0], row=2, col=2) + + fig.update_layout(title='All Data Dashboard', height=800) + # fig.write_html('first_figure.html', auto_open=True) + return fig + +# red_dashboard() \ No newline at end of file