diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..8066b0e 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,19 +19,22 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return arr.mean() def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return arr*2 def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return arr[arr > 25] # ============================================================ @@ -42,7 +45,8 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 - pass + prices_arr = np.array(prices) + return len(prices_arr[prices_arr > 1000]) def yellow_top3_stock_indices(stocks): @@ -51,7 +55,9 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 - pass + stocks_arr = np.array(stocks) + stocks_arr = np.argsort(stocks)[::-1] + return stocks_arr[:3] def yellow_restock_cost(prices, stocks): @@ -60,7 +66,8 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 - pass + total = prices[prices < 500] *50 + return total.sum() # ============================================================ @@ -77,4 +84,8 @@ def red_double11_prices(prices, stocks): 提示:np.where 可以巢狀使用 """ # TODO: 你的程式碼 - pass + prices_07 = prices[stocks >= 100] * 0.7 + prices_09 = prices[(stocks >= 20) & (stocks < 100)] * 0.9 + prices_original = prices[stocks < 20] + final_price = np.concatenate([prices_original, prices_07, prices_09]) + return final_price diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..cc6ee97 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 + DATA = "/datasets/ecommerce/orders_raw.csv" + df = pd.read_csv(DATA) + return df def green_shape(df): @@ -29,7 +31,8 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - pass + + return df.shape def green_dtypes(df): @@ -38,7 +41,8 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - pass + + return df.dtypes # ============================================================ @@ -52,7 +56,9 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 - pass + + df.columns = df.columns.str.strip().str.lower() + return df def yellow_clean_amount(df): @@ -63,7 +69,12 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + arr = (df['amount'] + .astype(str) + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float)) + return arr def yellow_drop_duplicates(df): @@ -72,7 +83,9 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass + + arr = df.drop_duplicates() + return arr # ============================================================ @@ -93,4 +106,16 @@ 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'] + .astype(str) + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float)) + df["order_date"] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() + return df \ No newline at end of file diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..11fbed1 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,19 +24,42 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass - + orders = pd.read_csv("datasets/ecommerce/orders_clean.csv") + customers = pd.read_csv("datasets/ecommerce/customers.csv") + products = pd.read_csv("datasets/ecommerce/products.csv") + df = ( + orders + .merge(customers, on = 'customer_id', how='left') + .merge(products, on = 'product_id', how='left') + ) + return df def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 - pass + orders = pd.read_csv("datasets/ecommerce/orders_clean.csv") + customers = pd.read_csv("datasets/ecommerce/customers.csv") + products = pd.read_csv("datasets/ecommerce/products.csv") + df = ( + orders + .merge(customers, on = 'customer_id', how='left') + .merge(products, on = 'product_id', how='left') + ) + return df.shape[0] def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 - pass + orders = pd.read_csv("datasets/ecommerce/orders_clean.csv") + customers = pd.read_csv("datasets/ecommerce/customers.csv") + products = pd.read_csv("datasets/ecommerce/products.csv") + df = ( + orders + .merge(customers, on = 'customer_id', how='left') + .merge(products, on = 'product_id', how='left') + ) + return df.columns # ============================================================ @@ -50,7 +73,9 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + + amount = df.groupby('category')['amount'].sum().sort_values(ascending=False).head(1) + return amount def yellow_gold_vip_stats(df): @@ -60,7 +85,12 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + + vip = df[df['vip_level'] == 'Gold'] + vip_sum = len(vip['order_id']) + vip_amount = float(vip['amount'].sum()) + Gold_Vip = (vip_sum, vip_amount) + return Gold_Vip def yellow_region_avg_amount(df): @@ -70,7 +100,9 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + + Series = df.groupby('region')['amount'].mean() + return Series # ============================================================ @@ -94,4 +126,38 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + orders = pd.read_csv("datasets/ecommerce/orders_clean.csv") + customers = pd.read_csv("datasets/ecommerce/customers.csv") + products = pd.read_csv("datasets/ecommerce/products.csv") + # R = orders.groupby('customer_id')['order_date'].max() + # F = orders.groupby('customer_id')['order_id'].count() + # M = orders.groupby('customer_id')['amount'].sum().sort_values(ascending=False).head() + # df = ( + # orders + # .merge(customers, on = 'customer_id', how='left') + # .merge(products, on = 'product_id', how='left') + # ) + RFM = ( + orders.groupby('customer_id') + .agg( + recency = ('order_date', 'max'), + frequency = ('order_id', 'count'), + monetary = ('amount', 'sum'), + ) + .reset_index() +) + rfm_named = RFM.merge( + customers[['customer_id', 'customer_name']], + on = 'customer_id', + how='left', +) + + final_5 = ( + rfm_named + .sort_values("M", ascending=False) + .head(5) + .reset_index(drop=True) + [['customer_id', 'customer_name', 'R', 'F', 'M']] +) + + return final_5 \ No newline at end of file diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..9306551 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -27,7 +27,11 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass + df = _load_data() + + result = df.groupby(df["order_date"].dt.month)['amount'].mean().round(1) + + return result def green_top3_dates(): @@ -37,7 +41,9 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = _load_data() + top3 = df['order_date'].dt.date.value_counts().head(3) + return top3 def green_date_range(): @@ -46,7 +52,11 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - pass + df = _load_data() + time_1 = df['order_date'].min() + time_2 = df['order_date'].max() + result = (time_1, time_2) + return result # ============================================================ @@ -60,7 +70,9 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 - pass + df = _load_data() + test = df.set_index('order_date').resample('ME')['amount'].sum() + return test def yellow_rolling_avg(monthly_revenue): @@ -71,7 +83,8 @@ 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,7 +94,8 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + + return df.groupby('category')['amount'].median().sort_values(ascending=False) # ============================================================ @@ -101,4 +115,18 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = _load_data() + df['year_mon'] = df['order_date'].dt.to_period('M') + monthly_report = (df.groupby('year_mon') + .agg( + 當月訂單數 = ('order_id', 'count'), + 當月總營收 = ('amount', 'sum'), + 單月不重複客戶數 = ('customer_id', 'nunique'), + ) + .sort_index() + ) + monthly_report['客單價'] = (monthly_report['當月總營收'] / monthly_report['當月訂單數']) + monthly_report['月營收成長率'] = (monthly_report['當月總營收'].pct_change()*100) + monthly_report = monthly_report.reset_index().rename(columns={'year_mon' : '月份'}) + monthly_report['月份'] = monthly_report['月份'].astype(str) + return monthly_report diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..38b4cee 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -29,7 +29,19 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 - pass + df = _load_data() + counts = df['category'].value_counts().reset_index() + counts.columns = ['category', 'order_count'] + figure = plt.figure(figsize=(8, 4)) + sns.barplot( + data=counts, + x='category', y='order_count', + hue='category', palette='viridis', legend=False, + ) + + plt.tight_layout() + return figure + def green_hist_amount(): @@ -39,7 +51,14 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass + df = _load_data() + figure = plt.figure(figsize=(9, 4)) + sns.histplot(data=df, x='amount', bins=20, kde=True, color='green') + plt.title('Order Amount Distribution', fontweight='bold') + plt.xlabel('Amount (NT$)') + plt.ylabel('Frequency') + plt.tight_layout() + return figure def green_set_labels(): @@ -51,7 +70,23 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + df = _load_data() + counts = df['category'].value_counts().reset_index() + counts.columns = ['category', 'order_count'] + figure = plt.figure(figsize=(8, 4)) + sns.barplot( + data=counts, + x='category', y='order_count', + hue='category', palette='viridis', legend=False, + ) + plt.title('Order Count by Category', fontweight='bold') + plt.xlabel('Category') + plt.ylabel('Order Count') + for i, v in enumerate(counts['order_count']): + plt.text(i, v, f'{v:,}', ha='center', va='bottom', fontsize=10) + + plt.tight_layout() + return figure # ============================================================ @@ -68,7 +103,27 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = _load_data() + ns_df = df[df['region'].isin(['North', 'South'])] + monthly_ns = ( + ns_df.groupby(['month', 'region'])['amount'] + .sum() + .reset_index() + ) + + figure = plt.figure(figsize=(10, 4)) + sns.lineplot( + data=monthly_ns, + x='month', y='amount', + hue='region', marker='o', linewidth=2, +) + plt.title('Monthly Revenue: North vs South', fontweight='bold') + plt.xlabel('Month') + plt.ylabel('Revenue (NT$)') + plt.xticks(rotation=45) + plt.legend(title='Region') + plt.tight_layout() + return figure def yellow_box_vip(): @@ -78,7 +133,18 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass + df = _load_data() + figure = plt.figure(figsize=(9, 5)) + sns.boxplot( + data=df, + x='vip_level', y='amount', + hue='vip_level', palette='Set3', legend=False, + ) + plt.title('Order Amount Distribution by VIP Level', fontweight='bold') + plt.xlabel('VIP Level') + plt.ylabel('Amount (NT$)') + plt.tight_layout() + return figure def yellow_scatter_price_amount(): @@ -88,7 +154,17 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass + df = _load_data() + figure = plt.figure(figsize=(9, 5)) + sns.scatterplot( + data=df, + x='unit_price', y='amount', + ) + plt.title('Unit_price Amount Distribution by Category', fontweight='bold') + plt.xlabel('Unit Price') + plt.ylabel('Amount (NT$)') + plt.tight_layout() + return figure # ============================================================ @@ -107,4 +183,55 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass + df = _load_data() + elec = df[df['category'] == category].copy() + elec_monthly = elec.groupby('month')['amount'].sum().reset_index() + elec_region = ( + elec.groupby('region')['amount'] + .sum() + .sort_values(ascending=False) + .reset_index() + ) + elec_top5 = ( + elec.groupby('product_name')['amount'] + .sum() + .sort_values(ascending=False) + .head(5) + .reset_index() + ) + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle('Electronics Category Dashboard', fontsize=16, fontweight='bold') + sns.lineplot( + data=elec_monthly, x='month', y='amount', + marker='o', linewidth=2, color='#1f77b4', ax=axes[0, 0], + ) + axes[0, 0].set_title('Monthly Revenue Trend') + axes[0, 0].set_xlabel('Month') + axes[0, 0].set_ylabel('Revenue (NT$)') + axes[0, 0].tick_params(axis='x', rotation=45) + sns.barplot( + data=elec_region, x='region', y='amount', + hue='region', palette='viridis', legend=False, ax=axes[0, 1], +) + axes[0, 1].set_title('Revenue by Region') + axes[0, 1].set_xlabel('Region') + axes[0, 1].set_ylabel('Revenue (NT$)') + for i, v in enumerate(elec_region['amount']): + axes[0, 1].text(i, v, f'{v:,.0f}', ha='center', va='bottom', fontsize=9) + sns.barplot( + data=elec_top5, y='product_name', x='amount', + hue='product_name', palette='magma', legend=False, ax=axes[1, 0], + ) + axes[1, 0].set_title('Top 5 Products') + axes[1, 0].set_xlabel('Revenue (NT$)') + axes[1, 0].set_ylabel('Product') + sns.histplot( + data=elec, x='amount', bins=25, kde=True, + color='#d62728', ax=axes[1, 1], + ) + axes[1, 1].set_title('Amount Distribution') + axes[1, 1].set_xlabel('Amount (NT$)') + axes[1, 1].set_ylabel('Frequency') + + plt.tight_layout() + return fig diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..4b8329c 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,7 +26,15 @@ def green_plotly_bar(): 提示:px.bar() """ # TODO: 你的程式碼 - pass + df = pd.read_csv( + 'datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + region_rev = df.groupby('category', as_index=False)['amount'].sum().sort_values('amount', ascending=False) + fig = px.bar(region_rev, x='category', y='amount', text='amount', + color='category', title='Revenue by Category') + fig.update_traces(texttemplate='%{text:,.0f}', textposition='outside') + fig.update_layout(height=400, showlegend=False) + return fig def green_plotly_line(): @@ -37,7 +45,16 @@ def green_plotly_line(): 提示:先 groupby 月份算總營收,再 px.line() """ # TODO: 你的程式碼 - pass + df = pd.read_csv( + 'datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + df['month'] = df['order_date'].dt.to_period('M').astype(str) + monthly = df.groupby('month', as_index=False)['amount'].sum() + + fig = px.line(monthly, x='month', y='amount', markers=True, + title='Monthly Revenue Trend') + fig.update_layout(height=400) + return fig def green_plotly_pie(): @@ -48,7 +65,14 @@ def green_plotly_pie(): 提示:px.pie() """ # TODO: 你的程式碼 - pass + df = pd.read_csv( + 'datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + vip_rev = df.groupby('vip_level', as_index=False)['amount'].sum() + fig = px.pie(vip_rev, names='vip_level', values='amount', + title='VIP Level Share', hole=0.4) + fig.update_layout(height=400) + return fig # ============================================================ @@ -63,7 +87,29 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 回傳:合併後的 DataFrame """ # TODO: 你的程式碼 - pass + df = pd.read_csv( + raw_path, + parse_dates=['order_date'],) + df.columns = df.columns.str.strip().str.lower() + df['amount'] = ( + df['amount'].astype(str) + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float) + ) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + + enriched = ( + df + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') +) + return enriched def yellow_kpi_summary(df): @@ -77,7 +123,35 @@ def yellow_kpi_summary(df): } """ # TODO: 你的程式碼 - pass + df = pd.read_csv('datasets/ecommerce/orders_raw.csv', + parse_dates=['order_date'],) + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/products.csv') + df.columns = df.columns.str.strip().str.lower() + df['amount'] = ( + df['amount'].astype(str) + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float) + ) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() + + enriched = ( + df + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + kpis = { + '總營收': enriched['amount'].sum(), + '總訂單數': len(enriched), + '活躍顧客數': enriched['customer_id'].nunique(), + '客單價': enriched['amount'].sum() / len(enriched), + } + + return kpis def yellow_plotly_scatter(df): @@ -91,7 +165,15 @@ def yellow_plotly_scatter(df): 提示:px.scatter(hover_data=['product_name']) """ # TODO: 你的程式碼 - pass + fig = px.scatter( + df, + x='unit_price', + y='amount', + color='category', + hover_data=['product_name'], + title='Unit Price vs. Amount by Category' + ) + return fig # ============================================================ @@ -115,4 +197,54 @@ def red_dashboard(): 提示:from plotly.subplots import make_subplots """ # TODO: 你的程式碼 - pass + df = pd.read_csv('datasets/ecommerce/orders_raw.csv', + parse_dates=['order_date'],) + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/products.csv') + df.columns = df.columns.str.strip().str.lower() + df['amount'] = ( + df['amount'].astype(str) + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float) + ) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() + + enriched = ( + df + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + monthly = enriched.groupby('month', as_index=False)['amount'].sum() + top_prod = (enriched.groupby('product_name', as_index=False)['amount'] + .sum().sort_values('amount', ascending=False).head(10)) + region_rev = enriched.groupby('region', as_index=False)['amount'].sum() + cat_rev = enriched.groupby('category', as_index=False)['amount'].sum() + fig = make_subplots( + rows=2, cols=2, + subplot_titles=('Monthly Revenue Trend', + 'Top 10 Products', + 'Revenue by Region', + 'Category Share'), + specs=[[{'type': 'xy'}, {'type': 'xy'}], + [{'type': 'xy'}, {'type': 'domain'}]], + ) + + fig.add_trace(go.Scatter(x=monthly['month'], y=monthly['amount'], + mode='lines+markers', name='Monthly'), row=1, col=1) + fig.add_trace(go.Bar(x=top_prod['product_name'], y=top_prod['amount'], + name='Top Products'), row=1, col=2) + fig.add_trace(go.Bar(x=region_rev['region'], y=region_rev['amount'], + name='Region'), row=2, col=1) + fig.add_trace(go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], + name='Category', hole=0.4), row=2, col=2) + + fig.update_layout( + title_text='E-Commerce Sales Dashboard — 2025【解答版】', + height=750, showlegend=False, + ) + fig.update_xaxes(tickangle=45, row=1, col=2) + return fig