1 代碼的選擇和改進
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from ipywidgets import (AppLayout, Dropdown, Button, Output, VBox, HBox, Label, Layout, SelectMultiple,IntSlider, FloatSlider, Checkbox, Text, Select)
from IPython.display import display, clear_output
import numpy as np
import os
from datetime import datetime
from traitlets import TraitError# 設置中文字體和樣式
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC", "sans-serif"]
plt.rcParams["axes.unicode_minus"] = False
sns.set_style("whitegrid")
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = Falseclass EnhancedExcelVisualizer:def __init__(self, file_path1, file_path2):"""初始化增強版Excel數據可視化應用"""self.file_path1 = file_path1self.file_path2 = file_path2# 存儲證券代碼篩選狀態self.selected_codes = []self.current_figure = None# 嘗試加載文件try:self.excel_file1 = pd.ExcelFile(file_path1)self.excel_file2 = pd.ExcelFile(file_path2)self.sheet_names1 = self.excel_file1.sheet_namesself.sheet_names2 = self.excel_file2.sheet_namesexcept Exception as e:print(f"文件加載錯誤: {str(e)}")return# 當前狀態變量self.current_data = Noneself.current_sheet = Noneself.current_file = Noneself.current_chart_type = 'line'self.figsize = (10, 6)self.color_palette = 'viridis'# 創建UI組件self.create_ui_components()# 顯示UIself.display_ui()# 初始化self.update_sheet_selector()def create_ui_components(self):"""創建所有UI組件"""# 文件選擇器file1_name = os.path.basename(self.file_path1)file2_name = os.path.basename(self.file_path2)self.file_selector = Dropdown(options=[f'文件1 ({file1_name})', f'文件2 ({file2_name})'],value=f'文件1 ({file1_name})',description='選擇文件:',layout=Layout(width='100%'))# 工作表選擇器self.sheet_selector = Dropdown(options=[],description='選擇工作表:',layout=Layout(width='100%'))# 數據列選擇器self.x_axis_selector = Dropdown(options=[],description='X軸數據列:',layout=Layout(width='100%'))self.y_axis_selector = SelectMultiple(options=[],description='Y軸數據列:',layout=Layout(width='100%'))# 證券代碼篩選器self.code_selector = Text(value='589990.SH,589980.SH,589900.SH',description='證券代碼 (逗號分隔):',layout=Layout(width='100%'))# 圖表類型選擇器self.chart_type_selector = Dropdown(options=['折線圖', '柱狀圖', '面積圖', '散點圖', '箱線圖', '熱力圖'],value='折線圖',description='圖表類型:',layout=Layout(width='100%'))# 圖表樣式控制self.color_palette_selector = Dropdown(options=['viridis', 'plasma', 'inferno', 'magma', 'cividis', 'Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2'],value='viridis',description='配色方案:',layout=Layout(width='100%'))self.figsize_slider = IntSlider(min=5, max=20, value=10, step=1,description='圖表大小:',layout=Layout(width='100%'))self.rotation_slider = IntSlider(min=0, max=90, value=45, step=5,description='標簽旋轉:',layout=Layout(width='100%'))self.transparency_slider = FloatSlider(min=0.1, max=1.0, value=0.8, step=0.1,description='透明度:',layout=Layout(width='100%'))self.grid_checkbox = Checkbox(value=True,description='顯示網格',layout=Layout(width='100%'))# 操作按鈕self.refresh_button = Button(description='刷新圖表',button_style='primary',icon='refresh',layout=Layout(width='100%'))self.export_button = Button(description='導出圖表',button_style='info',icon='save',layout=Layout(width='100%'))# 輸出區域self.data_preview_output = Output(layout=Layout(height='auto', max_height='300px', overflow='auto'))self.chart_output = Output(layout=Layout(height='500px', border='1px solid #e0e0e0'))self.stats_output = Output(layout=Layout(height='auto', max_height='300px', overflow='auto'))self.log_output = Output(layout=Layout(height='auto', max_height='200px', overflow='auto'))# 綁定事件self.file_selector.observe(self.on_file_change, names='value')self.sheet_selector.observe(self.on_sheet_change, names='value')self.chart_type_selector.observe(self.on_chart_type_change, names='value')self.refresh_button.on_click(self.refresh_chart)self.export_button.on_click(self.export_chart)self.code_selector.observe(self.on_code_change, names='value')# 樣式控制綁定controls = [self.color_palette_selector, self.figsize_slider, self.rotation_slider, self.transparency_slider,self.grid_checkbox]for control in controls:control.observe(self.on_style_change, names='value')def on_file_change(self, change):"""處理文件選擇變化事件"""with self.log_output:print(f"已選擇文件: {change.new}")self.update_sheet_selector()def on_sheet_change(self, change):"""處理工作表選擇變化事件"""with self.log_output:print(f"已選擇工作表: {change.new}")self.update_column_selectors()def on_chart_type_change(self, change):"""處理圖表類型變化事件"""with self.log_output:print(f"已選擇圖表類型: {change.new}")self.refresh_chart()def on_code_change(self, change):"""證券代碼篩選變化事件處理"""codes = change.new.strip()if codes:self.selected_codes = [code.strip() for code in codes.split(',') if code.strip()]with self.log_output:print(f"已選擇證券代碼: {', '.join(self.selected_codes)}")else:self.selected_codes = []with self.log_output:print("未選擇證券代碼")self.refresh_chart()def on_style_change(self, change):"""處理樣式變化事件"""with self.log_output:print(f"樣式設置已更新: {change.owner.description} = {change.new}")self.refresh_chart()def update_sheet_selector(self):"""更新工作表選擇器選項"""selected_file = self.file_selector.value.split(' ')[0]if selected_file == '文件1':self.sheet_selector.options = self.sheet_names1else:self.sheet_selector.options = self.sheet_names2if self.sheet_selector.options:self.sheet_selector.value = self.sheet_selector.options[0]def update_column_selectors(self):"""更新數據列選擇器選項"""selected_file = self.file_selector.value.split(' ')[0]selected_sheet = self.sheet_selector.valueif not selected_sheet:self.x_axis_selector.options = []self.y_axis_selector.options = []returntry:if selected_file == '文件1':self.current_data = self.excel_file1.parse(selected_sheet)else:self.current_data = self.excel_file2.parse(selected_sheet)# 如果存在ts_code列且有篩選代碼,則進行篩選if 'ts_code' in self.current_data.columns and self.selected_codes:self.current_data = self.current_data[self.current_data['ts_code'].isin(self.selected_codes)]except Exception as e:with self.log_output:print(f"讀取工作表數據時出錯: {str(e)}")returnself.current_sheet = selected_sheetself.current_file = selected_file# 更新列選擇器column_names = list(self.current_data.columns)self.x_axis_selector.options = column_namesself.y_axis_selector.options = column_names# 自動選擇默認列default_x = 'trade_date' if 'trade_date' in column_names else column_names[0]default_y = ['close'] if 'close' in column_names else (column_names[1:] if len(column_names) > 1 else [])self.x_axis_selector.value = default_xself.y_axis_selector.value = tuple(default_y) if default_y else ()# 更新數據預覽和統計信息self.update_data_preview()self.update_stats()def update_data_preview(self):"""更新數據預覽"""with self.data_preview_output:clear_output()if self.current_data is not None:display(self.current_data.head())def update_stats(self):"""更新統計信息"""with self.stats_output:clear_output()if self.current_data is not None:display(self.current_data.describe(include='all'))def refresh_chart(self, b=None):"""刷新圖表顯示"""x_axis = self.x_axis_selector.valuey_axes = self.y_axis_selector.valueif not x_axis or not y_axes or self.current_data is None:with self.chart_output:clear_output()print('請選擇有效的X軸和Y軸數據列')returnwith self.chart_output:clear_output()plt.close('all') # 清除之前的圖形try:# 數據預處理if 'ts_code' in self.current_data.columns and self.selected_codes:filtered_data = self.current_data[self.current_data['ts_code'].isin(self.selected_codes)].dropna(subset=[x_axis] + list(y_axes)).copy()else:filtered_data = self.current_data.dropna(subset=[x_axis] + list(y_axes)).copy()if filtered_data.empty:print('所選列中沒有有效數據或無匹配的證券代碼')return# 設置圖表大小self.figsize = (self.figsize_slider.value, self.figsize_slider.value*0.6)plt.figure(figsize=self.figsize)# 設置顏色sns.set_palette(self.color_palette_selector.value)# 圖表類型處理chart_type = self.chart_type_selector.valueif chart_type == '折線圖':self._draw_line_plot(filtered_data, x_axis, y_axes)elif chart_type == '柱狀圖':self._draw_bar_plot(filtered_data, x_axis, y_axes)elif chart_type == '面積圖':self._draw_area_plot(filtered_data, x_axis, y_axes)elif chart_type == '散點圖':self._draw_scatter_plot(filtered_data, x_axis, y_axes)elif chart_type == '箱線圖':self._draw_box_plot(filtered_data, x_axis, y_axes)elif chart_type == '熱力圖':self._draw_heatmap(filtered_data, x_axis, y_axes)# 通用設置plt.grid(self.grid_checkbox.value)plt.tight_layout()self.current_figure = plt.gcf()plt.show()except Exception as e:with self.log_output:print(f"繪制圖表時出錯: {str(e)}")import tracebacktraceback.print_exc()def _draw_line_plot(self, data, x_axis, y_axes):"""繪制折線圖"""# 嘗試轉換日期數據try:data[x_axis] = pd.to_datetime(data[x_axis])data = data.sort_values(by=x_axis)date_flag = Trueexcept:date_flag = Falseif 'ts_code' in data.columns and len(self.selected_codes) > 1:# 按證券代碼分組繪制for code, group in data.groupby('ts_code'):for y_axis in y_axes:if pd.api.types.is_numeric_dtype(group[y_axis]):sns.lineplot(x=x_axis, y=y_axis, data=group,alpha=self.transparency_slider.value,label=f"{code}-{y_axis}")else:for y_axis in y_axes:if pd.api.types.is_numeric_dtype(data[y_axis]):sns.lineplot(x=x_axis, y=y_axis, data=data,alpha=self.transparency_slider.value,label=y_axis)plt.title(f'折線圖: {", ".join(y_axes)} vs {x_axis}')plt.xlabel(x_axis)plt.ylabel('值')if date_flag:plt.xticks(rotation=self.rotation_slider.value)if len(y_axes) > 1 or ('ts_code' in data.columns and len(self.selected_codes) > 1):plt.legend(bbox_to_anchor=(1, 1), loc='upper left')def _draw_bar_plot(self, data, x_axis, y_axes):"""繪制柱狀圖"""if len(y_axes) != 1:with self.log_output:print('柱狀圖需要且僅需要一個Y軸')returny_axis = y_axes[0]if 'ts_code' in data.columns and len(self.selected_codes) > 1:# 按證券代碼分組繪制for code, group in data.groupby('ts_code'):if pd.api.types.is_numeric_dtype(group[y_axis]):sns.barplot(x=x_axis, y=y_axis, data=group,alpha=self.transparency_slider.value,label=code)else:if pd.api.types.is_numeric_dtype(data[y_axis]):sns.barplot(x=x_axis, y=y_axis, data=data,alpha=self.transparency_slider.value)plt.title(f'柱狀圖: {y_axis} 按 {x_axis} 分布')plt.xlabel(x_axis)plt.ylabel(y_axis)plt.xticks(rotation=self.rotation_slider.value)if 'ts_code' in data.columns and len(self.selected_codes) > 1:plt.legend(bbox_to_anchor=(1, 1), loc='upper left')def _draw_area_plot(self, data, x_axis, y_axes):"""繪制面積圖"""if len(y_axes) != 1:with self.log_output:print('面積圖需要且僅需要一個Y軸')returny_axis = y_axes[0]if 'ts_code' in data.columns and len(self.selected_codes) > 1:# 按證券代碼分組繪制for code, group in data.groupby('ts_code'):if pd.api.types.is_numeric_dtype(group[y_axis]):plt.fill_between(group[x_axis], group[y_axis],alpha=self.transparency_slider.value,label=code)else:if pd.api.types.is_numeric_dtype(data[y_axis]):plt.fill_between(data[x_axis], data[y_axis],alpha=self.transparency_slider.value)plt.title(f'面積圖: {y_axis} 按 {x_axis} 分布')plt.xlabel(x_axis)plt.ylabel(y_axis)plt.xticks(rotation=self.rotation_slider.value)if 'ts_code' in data.columns and len(self.selected_codes) > 1:plt.legend(bbox_to_anchor=(1, 1), loc='upper left')def _draw_scatter_plot(self, data, x_axis, y_axes):"""繪制散點圖"""if len(y_axes) != 1:with self.log_output:print('散點圖需要且僅需要一個Y軸')returny_axis = y_axes[0]if 'ts_code' in data.columns and len(self.selected_codes) > 1:# 按證券代碼分組繪制for code, group in data.groupby('ts_code'):if pd.api.types.is_numeric_dtype(group[x_axis]) and pd.api.types.is_numeric_dtype(group[y_axis]):sns.scatterplot(x=x_axis, y=y_axis, data=group,alpha=self.transparency_slider.value,label=code)else:if pd.api.types.is_numeric_dtype(data[x_axis]) and pd.api.types.is_numeric_dtype(data[y_axis]):sns.scatterplot(x=x_axis, y=y_axis, data=data,alpha=self.transparency_slider.value)plt.title(f'散點圖: {y_axis} vs {x_axis}')plt.xlabel(x_axis)plt.ylabel(y_axis)if 'ts_code' in data.columns and len(self.selected_codes) > 1:plt.legend(bbox_to_anchor=(1, 1), loc='upper left')def _draw_box_plot(self, data, x_axis, y_axes):"""繪制箱線圖"""if len(y_axes) != 1:with self.log_output:print('箱線圖需要且僅需要一個Y軸')returny_axis = y_axes[0]if 'ts_code' in data.columns and len(self.selected_codes) > 1:# 按證券代碼分組繪制for code, group in data.groupby('ts_code'):if pd.api.types.is_numeric_dtype(group[y_axis]):sns.boxplot(x=group[x_axis], y=y_axis, data=group,alpha=self.transparency_slider.value,label=code)else:if pd.api.types.is_numeric_dtype(data[y_axis]):sns.boxplot(x=x_axis, y=y_axis, data=data)plt.title(f'箱線圖: {y_axis} 按 {x_axis} 分布')plt.xlabel(x_axis)plt.ylabel(y_axis)plt.xticks(rotation=self.rotation_slider.value)if 'ts_code' in data.columns and len(self.selected_codes) > 1:plt.legend(bbox_to_anchor=(1, 1), loc='upper left')def _draw_heatmap(self, data, x_axis, y_axes):"""繪制熱力圖"""if len(y_axes) < 2:with self.log_output:print('熱力圖需要至少兩個Y軸')return# 選擇數值列numeric_cols = [col for col in y_axes if pd.api.types.is_numeric_dtype(data[col])]if len(numeric_cols) < 2:with self.log_output:print('熱力圖需要至少兩個數值列')return# 計算相關系數corr = data[numeric_cols].corr()# 繪制熱力圖sns.heatmap(corr, annot=True, cmap=self.color_palette_selector.value,fmt=".2f", linewidths=.5)plt.title('熱力圖: 相關系數矩陣')plt.xticks(rotation=self.rotation_slider.value)plt.yticks(rotation=0)def export_chart(self, b):"""導出圖表"""if not hasattr(self, 'current_figure'):with self.log_output:print('沒有可導出的圖表')return# 包含證券代碼信息在文件名中code_str = '_'.join(self.selected_codes) if self.selected_codes else 'all_codes'chart_type = self.chart_type_selector.valuetimestamp = datetime.now().strftime("%Y%m%d_%H%M%S")filename = f"chart_{chart_type}_{code_str}_{timestamp}.png"try:self.current_figure.savefig(filename, dpi=300, bbox_inches='tight')with self.log_output:print(f'圖表已成功導出為: {filename}')except Exception as e:with self.log_output:print(f'導出圖表時出錯: {str(e)}')def display_ui(self):"""顯示應用界面"""# 控制面板control_panel = VBox([self.file_selector,self.sheet_selector,self.x_axis_selector,self.y_axis_selector,self.code_selector,self.chart_type_selector,self.color_palette_selector,self.figsize_slider,self.rotation_slider,self.transparency_slider,self.grid_checkbox,HBox([self.refresh_button, self.export_button])], layout=Layout(width='30%', padding='10px'))# 輸出面板output_panel = VBox([Label('📊 數據預覽'),self.data_preview_output,Label('📈 數據統計'),self.stats_output,Label('? 數據可視化'),self.chart_output,Label('📝 日志信息'),self.log_output], layout=Layout(width='70%', padding='10px'))# 整體布局app_layout = HBox([control_panel, output_panel])# 顯示應用display(app_layout)# 使用示例
if __name__ == "__main__":# 修改為您的Excel文件路徑file_path1 = 'G:\\PyCharm\\Python_project\\daily_chart.xlsx'file_path2 = 'G:\\PyCharm\\Python_project\\daily_market_data.xlsx'# 創建并顯示應用app = EnhancedExcelVisualizer(file_path1, file_path2)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime# 設置中文顯示和美觀的圖表樣式
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用來正常顯示中文標簽
plt.rcParams['axes.unicode_minus'] = False # 用來正常顯示負號
sns.set_style("whitegrid", {'font.sans-serif':['simhei','Arial']})# 1. 數據加載 - 修改后支持多個文件
def load_data():# 指定文件路徑列表file_paths = [r'G:\PyCharm\Python_project\output.xlsx',r'G:\PyCharm\Python_project\output1.xlsx',r'G:\PyCharm\Python_project\output3.xlsx']all_data = []# 循環讀取每個文件for file_path in file_paths:try:# 讀取Excel文件df = pd.read_excel(file_path, sheet_name='Sheet1')# 轉換日期格式(假設原始日期是整數格式如20250627)df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')# 添加市場類型列(SH表示上海,SZ表示深圳)df['market'] = df['ts_code'].str[-3:]# 添加文件名信息,便于追蹤數據來源df['source_file'] = file_path.split('\\')[-1]all_data.append(df)print(f"成功加載文件: {file_path}")except Exception as e:print(f"加載文件 {file_path} 時出錯: {e}")# 合并所有數據if all_data:combined_df = pd.concat(all_data, ignore_index=True)print(f"已合并所有數據,總記錄數: {len(combined_df)}")return combined_dfelse:print("沒有成功加載任何數據!")return pd.DataFrame()# 2. 數據探索分析
def explore_data(df):if df.empty:print("沒有數據可分析!")returnprint("="*50)print("基本數據信息:")print(f"總記錄數: {len(df)}")print(f"日期范圍: {df['trade_date'].min()} 到 {df['trade_date'].max()}")print(f"唯一證券數量: {df['ts_code'].nunique()}")print(f"市場分布:\n{df['market'].value_counts()}")print("\n調整因子描述統計:")print(df['adj_factor'].describe())# 非1的調整因子統計non_one = df[df['adj_factor'] != 1]print(f"\n需要調整的證券數量: {len(non_one)} ({len(non_one)/len(df):.2%})")print("這些證券的描述統計:")print(non_one['adj_factor'].describe())# 按市場分類統計print("\n按市場分類的調整因子統計:")print(df.groupby('market')['adj_factor'].describe())# 按文件來源分類統計print("\n按文件來源分類的調整因子統計:")print(df.groupby('source_file')['adj_factor'].describe())# 找出調整因子最大和最小的10個證券top10 = df.nlargest(10, 'adj_factor')[['ts_code', 'trade_date', 'adj_factor', 'source_file']]bottom10 = df.nsmallest(10, 'adj_factor')[['ts_code', 'trade_date', 'adj_factor', 'source_file']]print("\n調整因子最大的10個證券:")print(top10)print("\n調整因子最小的10個證券:")print(bottom10)# 3. 數據可視化
def visualize_data(df):if df.empty:print("沒有數據可可視化!")return# 設置圖表大小plt.figure(figsize=(15, 10))# 調整因子分布直方圖plt.subplot(2, 2, 1)bins = np.logspace(np.log10(df['adj_factor'].min()), np.log10(df['adj_factor'].max()), 50)plt.hist(df['adj_factor'], bins=bins)plt.xscale('log')plt.title('調整因子分布(對數刻度)')plt.xlabel('調整因子(log scale)')plt.ylabel('數量')# 按市場分類的箱線圖plt.subplot(2, 2, 2)sns.boxplot(x='market', y='adj_factor', data=df)plt.title('不同市場的調整因子分布')plt.yscale('log') # 使用對數刻度以便更好地觀察# 時間變化分析(比較兩天的調整因子)if df['trade_date'].nunique() > 1:plt.subplot(2, 2, 3)changed = get_changed_securities(df)if not changed.empty:sns.scatterplot(x='adj_factor_prev', y='adj_factor', data=changed)plt.plot([0.1, 30], [0.1, 30], 'r--') # 添加對角線plt.xscale('log')plt.yscale('log')plt.title('調整因子變化情況(前后兩天比較)')plt.xlabel('前一天調整因子')plt.ylabel('當天調整因子')# 調整因子不為1的證券數量按市場分布plt.subplot(2, 2, 4)non_one = df[df['adj_factor'] != 1]if not non_one.empty:sns.countplot(x='market', data=non_one)plt.title('需要調整的證券數量按市場分布')plt.tight_layout()plt.show()# 新增圖表:按文件來源顯示調整因子分布plt.figure(figsize=(12, 6))sns.boxplot(x='source_file', y='adj_factor', data=df)plt.title('不同文件來源的調整因子分布')plt.yscale('log')plt.xticks(rotation=45)plt.tight_layout()plt.show()# 4. 找出調整因子發生變化的證券
def get_changed_securities(df):if df['trade_date'].nunique() < 2:print("數據中日期數量不足,無法比較調整因子變化!")return pd.DataFrame()# 獲取日期排序dates = sorted(df['trade_date'].unique())# 確保有足夠的日期進行比較if len(dates) < 2:print("數據中日期數量不足,無法比較調整因子變化!")return pd.DataFrame()# 合并前后兩天的數據prev_day = df[df['trade_date'] == dates[-2]].copy()curr_day = df[df['trade_date'] == dates[-1]].copy()merged = pd.merge(prev_day, curr_day, on='ts_code', suffixes=('_prev', ''), how='outer')# 只保留有變化的證券changed = merged[merged['adj_factor_prev'] != merged['adj_factor']]return changed# 5. 主函數
def main():# 加載數據df = load_data()# 數據探索explore_data(df)# 數據可視化visualize_data(df)# 找出變化的證券changed = get_changed_securities(df)if not changed.empty:print("\n調整因子發生變化的證券:")print(changed[['ts_code', 'adj_factor_prev', 'adj_factor', 'source_file']])print(f"\n調整因子變化的證券數量: {len(changed)}")else:print("\n沒有證券的調整因子發生變化")if __name__ == "__main__":main()
?
以下是對兩份代碼的詳細對比分析,從功能、架構、用戶體驗等多個維度梳理改進點與差異,并解釋為何最終選擇代碼 2:
一、功能對比與改進
1. 數據處理能力
-
代碼 1:
- 支持加載 2 個 Excel 文件,通過文件選擇器切換。
- 可篩選特定證券代碼(通過
ts_code
列),但需手動輸入逗號分隔的代碼。 - 數據預處理僅包含簡單的空值過濾,未對日期格式、市場類型等進行標準化處理。
-
代碼 2:
- 改進 1:支持加載多個文件(通過列表指定路徑),自動合并數據,提升數據處理效率。
- 改進 2:新增日期格式轉換(將整數日期轉為
datetime
類型),便于時間序列分析。 - 改進 3:添加
market
列(通過ts_code
后綴識別上海 / 深圳市場),支持按市場分類統計。 - 改進 4:添加
source_file
列追蹤數據來源,便于后續溯源和分析。
2. 數據分析深度
-
代碼 1:
- 核心功能為數據可視化,支持多種圖表類型(折線圖、柱狀圖等),但數據分析邏輯較少。
- 僅提供數據預覽和基礎統計描述(
describe()
),未針對業務場景(如調整因子)做專項分析。
-
代碼 2:
- 改進 5:新增
explore_data
函數,深度分析調整因子:- 統計需要調整的證券比例(
adj_factor != 1
)。 - 按市場、文件來源分組統計調整因子分布。
- 篩選調整因子最大 / 最小的證券,定位異常值。
- 統計需要調整的證券比例(
- 改進 6:新增
get_changed_securities
函數,對比前后兩天調整因子變化,識別波動證券。
- 改進 5:新增
二、可視化與交互設計差異
1. 可視化功能
-
代碼 1:
- 優勢:提供交互式圖表界面,支持動態切換圖表類型、配色、尺寸等參數。
- 不足:圖表邏輯分散在多個私有方法(如
_draw_line_plot
),維護性較差;部分圖表(如熱力圖)需手動指定多列,使用門檻較高。
-
代碼 2:
- 改進 7:可視化邏輯更聚焦業務場景,例如:
- 調整因子分布直方圖(對數刻度,適配寬范圍數據)。
- 按市場分類的箱線圖(對數刻度,突出異常值)。
- 調整因子變化散點圖(對角線輔助線,直觀展示波動)。
- 改進 8:圖表布局更緊湊(
subplot
分塊),一次性展示多維度分析結果,便于綜合判斷。
- 改進 7:可視化邏輯更聚焦業務場景,例如:
2. 交互方式
-
代碼 1:
- 采用 IPython 交互式組件(Dropdown、Slider 等),需在 Jupyter 環境中運行,適合實時探索。
- 操作流程較復雜(需依次選擇文件、工作表、坐標軸),新手使用可能存在學習成本。
-
代碼 2:
- 改進 9:采用腳本化執行方式,無需手動交互,一鍵運行即可輸出完整分析報告,適合批量處理。
- 改進 10:輸出內容包含文本統計結果和圖表,信息呈現更系統化,便于匯報和存檔。
三、架構與可維護性
1. 代碼結構
-
代碼 1:
- 采用類封裝(
EnhancedExcelVisualizer
),但方法耦合度較高(如 UI 組件創建與圖表繪制混合)。 - 異常處理較簡單,僅在文件加載時捕獲錯誤,其他環節(如數據處理、圖表繪制)的異常可能導致程序崩潰。
- 采用類封裝(
-
代碼 2:
- 改進 11:采用函數式編程,模塊職責清晰(
load_data
、explore_data
等),便于后續擴展。 - 改進 12:異常處理更全面,每個文件加載環節均有獨立的錯誤捕獲,避免單個文件失敗導致整個程序中斷。
- 改進 11:采用函數式編程,模塊職責清晰(
2. 可擴展性
-
代碼 1:
- 擴展新圖表類型需修改
refresh_chart
和對應繪圖方法,成本較高。 - 數據處理邏輯固定,難以適配不同格式的輸入數據。
- 擴展新圖表類型需修改
-
代碼 2:
- 改進 13:數據加載模塊可靈活擴展文件路徑列表,支持添加更多數據源。
- 改進 14:分析函數(如
explore_data
)可通過新增分組條件或統計指標快速擴展,適配不同業務需求。
四、為何最終選擇代碼 2?
1. 業務適配性更強
- 代碼 2 專門針對 “調整因子分析” 場景設計,包含從數據加載、探索到可視化的完整流程,直接解決業務問題。
- 代碼 1 更偏向通用數據可視化工具,缺乏對特定業務指標(如調整因子變化)的深度分析。
2. 執行效率與穩定性更高
- 代碼 2 采用腳本化執行,無需手動交互,適合批量處理多文件數據,執行效率更高。
- 模塊化設計和全面的異常處理確保程序穩定性,適合生產環境使用。
3. 結果呈現更清晰
- 代碼 2 的輸出包含文本統計和多維度圖表,信息組織更系統,便于業務人員理解和決策。
- 代碼 1 的交互式界面雖靈活,但需要用戶手動操作才能獲取結果,不適合生成標準化報告。
4. 維護與擴展成本更低
- 代碼 2 的函數式架構更簡潔,新增功能(如分析其他指標、接入新數據源)時只需修改對應函數,維護成本低。
- 代碼 1 的類封裝雖復雜,但 UI 組件與業務邏輯耦合,擴展時可能影響整體功能。
總結:改進與差異匯總表
對比維度 | 代碼 1 | 代碼 2(改進點) |
---|---|---|
數據處理 | 2 文件加載,簡單過濾 | 多文件合并,日期 / 市場標準化處理 |
業務分析 | 通用可視化,缺乏深度業務邏輯 | 聚焦調整因子分析,支持變化追蹤 |
可視化 | 交互式圖表,類型多樣 | 業務場景化圖表,對數刻度優化 |
交互方式 | Jupyter 交互式組件,手動操作 | 腳本化執行,一鍵生成報告 |
架構設計 | 類封裝,耦合度較高 | 函數式編程,模塊職責清晰 |
適用場景 | 數據探索與可視化實驗 | 業務指標分析、批量報告生成 |
代碼 2 通過更聚焦的業務邏輯、更穩健的架構和更高效的執行方式,更適合實際業務場景中的數據分析需求,因此最終選擇代碼 2。
2 模擬答辯
項目答辯過程日志
一、答辯基本信息
- 答辯時間:2025 年 6 月 30 日 14:00-14:30
- 答辯地點:線上會議室
- 答辯項目:基于 Python 的證券調整因子分析系統
- 參與評委:張教授、李老師、王工程師
二、答辯流程記錄
1. 開場陳述(5 分鐘)
- 自我介紹:簡要介紹自己的學習背景和項目參與經歷
- 項目背景:
- 證券市場中調整因子是復權計算的關鍵指標,直接影響價格走勢分析的準確性
- 現有工具缺乏對調整因子系統性分析,難以快速定位異常證券
- 項目目標:
- 開發一個能批量處理多源數據的調整因子分析系統
- 實現從數據加載、探索分析到可視化的全流程自動化
- 支持調整因子變化追蹤和異常值識別
- 選擇代碼 2 的原因:
- 相比代碼 1 更聚焦業務場景,能直接解決調整因子分析需求
- 模塊化設計更適合生產環境,可擴展性和維護性更強
- 腳本化執行方式適合批量處理,能快速生成標準化分析報告
2. 項目功能演示(10 分鐘)
(1)數據加載功能演示
- 展示代碼中文件路徑配置:
python
運行
file_paths = [r'G:\PyCharm\Python_project\output.xlsx',r'G:\PyCharm\Python_project\output1.xlsx',r'G:\PyCharm\Python_project\output3.xlsx'
]
- 演示數據加載過程,包括:
- 自動讀取多個 Excel 文件并合并數據
- 日期格式自動轉換(如 20250627 轉為 datetime 類型)
- 自動添加 market 列(根據 ts_code 后綴識別市場)
- 自動添加 source_file 列追蹤數據來源
- 展示加載成功后的日志輸出:
plaintext
成功加載文件: G:\PyCharm\Python_project\output.xlsx
成功加載文件: G:\PyCharm\Python_project\output1.xlsx
成功加載文件: G:\PyCharm\Python_project\output3.xlsx
已合并所有數據,總記錄數: 12543
(2)數據探索分析功能演示
- 運行 explore_data 函數,展示文本分析結果:
- 基本數據信息(記錄數、日期范圍、證券數量等)
- 調整因子描述統計
- 需要調整的證券數量及占比
- 按市場和文件來源分組的調整因子統計
- 調整因子最大和最小的 10 個證券列表
- 關鍵輸出示例:
plaintext
需要調整的證券數量: 3256 (25.97%)
調整因子最大的10個證券:ts_code trade_date adj_factor source_file
0 688001.SH 2025-06-27 5.234 output3.xlsx
1 688002.SH 2025-06-27 4.876 output1.xlsx
...
調整因子最小的10個證券:ts_code trade_date adj_factor source_file
0 000001.SZ 2025-06-27 0.125 output.xlsx
1 000002.SZ 2025-06-27 0.156 output.xlsx
...
(3)數據可視化功能演示
- 展示自動生成的多維度分析圖表:
- 調整因子分布直方圖(對數刻度)
- 按市場分類的調整因子箱線圖
- 調整因子變化散點圖(含對角線輔助線)
- 按市場分布的需要調整證券數量柱狀圖
- 按文件來源分類的調整因子箱線圖
- 特別說明圖表設計亮點:
- 使用對數刻度處理寬范圍數據,使分布特征更明顯
- 散點圖中添加對角線輔助線,直觀展示調整因子變化
- 子圖布局合理,便于綜合分析
(4)調整因子變化追蹤功能演示
- 運行 get_changed_securities 函數,展示:
- 前后兩天調整因子發生變化的證券列表
- 變化證券數量統計
- 示例輸出:
plaintext
調整因子發生變化的證券:ts_code adj_factor_prev adj_factor source_file
0 600001.SH 1.256 1.324 output.xlsx
1 600002.SH 2.345 2.112 output.xlsx
...
調整因子變化的證券數量: 876
3. 評委提問與回答(12 分鐘)
(1)張教授提問:
- 問題:為什么選擇函數式編程而不是類封裝方式?
- 回答:
- 函數式編程使模塊職責更清晰,每個函數專注于特定功能
- 便于后續擴展,新增功能只需添加新函數或修改對應函數
- 腳本化執行方式更適合批量處理和生成標準化報告
- 在這個特定業務場景下,函數式編程比類封裝更簡潔高效
(2)李老師提問:
- 問題:代碼中使用對數刻度的圖表設計是出于什么考慮?
- 回答:
- 調整因子的取值范圍通常較大,從 0.1 到 5 以上不等
- 線性刻度下,小值區間的分布特征會被壓縮難以觀察
- 使用對數刻度可以均勻展示整個取值范圍的分布情況
- 特別是在箱線圖中,對數刻度能更好地展示異常值
(3)王工程師提問:
- 問題:如果需要分析其他指標,這個系統容易擴展嗎?
- 回答:
- 系統采用模塊化設計,各功能解耦良好
- 擴展其他指標分析主要涉及:
- 在 load_data 函數中添加對新指標的處理
- 新增 explore_xxx 函數進行專項分析
- 新增 visualize_xxx 函數進行可視化展示
- 以調整因子分析模塊為參考,擴展其他指標難度較低
- 示例:若要分析收盤價,可以添加專門的收盤價分析函數
(4)張教授追問:
- 問題:相比代碼 1,代碼 2 在處理大數據量時表現如何?
- 回答:
- 代碼 1 采用交互式設計,在大數據量下可能會有性能問題
- 代碼 2 采用腳本化執行,一次性處理所有數據
- 關鍵優化點:
- 使用 pandas 的 concat 進行高效數據合并
- 避免不必要的交互式組件渲染
- 圖表生成時進行必要的數據過濾
- 在測試中,處理 10 萬條記錄時代碼 2 仍能保持良好性能
(5)李老師追問:
- 問題:系統在實際應用中可能存在哪些局限性?
- 回答:
- 目前假設所有文件都包含相同的列結構,若結構不同需要手動調整
- 調整因子變化分析僅比較前后兩天數據,更長時間跨度的分析需要擴展
- 圖表類型相對固定,復雜的可視化需求可能需要進一步開發
- 缺乏用戶界面,不適合非技術人員直接使用
4. 總結陳述(3 分鐘)
- 項目成果:
- 成功開發了一個功能完整的證券調整因子分析系統
- 實現了多源數據加載、深度分析和可視化展示
- 能夠快速定位調整因子異常證券和變化情況
- 技術亮點:
- 多文件批量處理和自動合并
- 業務場景化的深度分析和可視化
- 對數刻度等數據可視化優化
- 模塊化設計保證可擴展性
- 未來改進方向:
- 添加用戶界面,方便非技術人員使用
- 擴展更多分析維度和指標
- 增加數據導出功能,方便報告生成
- 優化大數據處理性能
三、答辯評價與反饋
1. 評委綜合評價
- 張教授:項目選題具有實際應用價值,代碼結構清晰,能夠很好地解決證券調整因子分析問題,對數刻度的圖表設計很有亮點。
- 李老師:數據處理流程完整,從加載到分析再到可視化形成了閉環,特別是調整因子變化追蹤功能很有實用價值。
- 王工程師:系統設計考慮了實際應用場景,模塊化設計使得系統具有較好的可擴展性,適合在實際業務中應用。
2. 改進建議
- 添加數據校驗功能,確保不同結構的文件能夠正確處理
- 增加分析結果導出功能,方便生成正式報告
- 考慮添加簡單的用戶界面,提升系統易用性
- 完善異常處理機制,提高系統穩定性
3. 答辯結果
- 答辯通過,評價等級:良好
- 建議根據評委意見進行改進后,可考慮在實際業務中試用
四、答辯總結與反思
1. 成功經驗
- 充分理解業務需求,確保項目具有實際應用價值
- 采用模塊化設計,提高系統可擴展性和可維護性
- 注重數據可視化效果,使用合適的圖表和刻度展示數據特征
- 提前準備充分,對項目的各個環節都有深入的理解
2. 不足之處
- 對系統在大數據量下的性能測試不夠充分
- 用戶界面設計方面考慮不足
- 異常處理機制還可以進一步完善
- 文檔說明不夠詳細,特別是函數參數和返回值的說明
3. 后續改進計劃
- 針對評委建議,逐步完善系統功能
- 進行大數據量測試,優化系統性能
- 設計簡單的用戶界面,提升系統易用性
- 完善文檔說明,方便后續維護和擴展
- 尋找實際應用場景,在實踐中檢驗和改進系統
通過本次答辯,不僅展示了項目成果,也獲得了寶貴的反饋意見。接下來將根據評委建議,進一步完善系統,使其更具實用性和推廣價值。
3 踩坑實錄
1 matplotlib 庫環境 換源
修改了生成 .condarc 文件,做了很多鏡像通道的嘗試...還是pip指令比較好
2 # 必須在 Jupyter Notebook 的第一行添加以下魔術指令
%matplotlib inline
3 數據跑不起來圖的原因測試中...:讀取?最小化測試通過,讀取的邏輯沒有問題繪制顯示? matplotlib驗證過了可以用,可以顯示
4 事件綁定刷新不同按鈕的時候 refresh邏輯優化這樣才能生成不同的圖表(可是有些數據本來就不能夠繪圖)
5 新的tkinter庫窗口結合數據,顯示讀取數據成功,但是依舊畫出空的圖像,有底圖,路徑也可以成功保存但是模擬數據(內置的寫死的)可以繪圖
.....哎
問題就是交互式頁面數據的結合,有點頭疼,怎么修復都沒發現,應該是數據代碼隱含的邏輯存在問題吧?
結合你之前在數據可視化和交互開發中遇到的問題,以下是針對文檔中提到的坑的詳細分析與解決方案,覆蓋環境配置、數據讀取、圖表繪制及交互邏輯等關鍵環節:
一、環境配置與鏡像問題
踩坑回顧
- 修改
.condarc
文件配置鏡像通道失敗,最終通過pip
指令換源解決。
解決方案
- 推薦 pip 換源方案(永久配置)
在命令行執行:bash
# 阿里云鏡像 pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ # 驗證配置 pip config list
- Jupyter 環境驗證
在 Notebook 中運行:python
運行
import matplotlib print(f"matplotlib版本: {matplotlib.__version__}") print(f"配置路徑: {matplotlib.get_configdir()}")
確保無ModuleNotFoundError
或版本沖突。
二、% matplotlib inline 魔術指令
踩坑回顧
- 需在 Notebook 第一行添加該指令,否則圖表可能無法顯示。
擴展說明
- 指令原理
%matplotlib inline
:將圖表嵌入 Notebook 單元格內,適合交互式探索。- 替代方案(如
%matplotlib tk
):彈出獨立窗口,但不利于報告生成。
- 最佳實踐
在 Notebook 開頭統一添加:python
運行
%matplotlib inline import matplotlib.pyplot as plt plt.rcParams["font.family"] = ["SimHei", "sans-serif"] # 解決中文顯示
三、數據讀取與圖表空白問題
踩坑回顧
- 單獨測試數據讀取和 matplotlib 繪圖正常,但結合交互邏輯后圖表為空。
深度排查步驟
- 數據有效性驗證
在繪圖前添加:python
運行
def plot_data(data):# 數據預處理檢查print(f"數據行數: {len(data)}")print(f"空值數量: {data.isna().sum().sum()}")print(f"數值列統計: {data.select_dtypes(include='number').describe()}")# 示例:繪制前5行數據if not data.empty:plt.plot(data.head()['x_col'], data.head()['y_col'])else:plt.text(0.5, 0.5, "數據為空", ha='center', va='center', transform=plt.gca().transAxes)
- 交互場景下的數據傳遞
- 若使用
tkinter
,需確認:- 數據是否通過
StringVar
/DataFrame
正確綁定到界面組件。 - 事件觸發時(如按鈕點擊)是否重新加載數據。
- 數據是否通過
- 示例:
Button(command=lambda: plot_data(df))
?需確保df
為最新數據。
- 若使用
四、事件綁定與 refresh 邏輯優化
踩坑回顧
- 刷新按鈕未正確觸發圖表更新,或因數據格式問題導致繪圖失敗。
優化方案
- 分層事件處理架構
python
運行
def on_refresh():try:# 1. 數據獲取data = load_data_from_ui()# 2. 數據校驗if data.empty:show_error("數據為空,請檢查輸入")return# 3. 圖表繪制update_chart(data)except Exception as e:show_error(f"繪圖錯誤: {str(e)}")def update_chart(data):plt.clf() # 清除舊圖表# 根據數據類型選擇繪圖方式if is_time_series(data):plot_time_series(data)else:plot_scatter(data)plt.draw()
- 錯誤狀態可視化
當數據無法繪圖時,顯示提示信息:python
運行
def plot_error():plt.clf()plt.text(0.5, 0.5, "無法繪制圖表\n請檢查數據格式", ha='center', va='center', fontsize=12)plt.axis('off')plt.draw()
五、tkinter 與數據可視化結合問題
踩坑回顧
- tkinter 窗口顯示數據讀取成功,但圖表為空;內置模擬數據可正常繪圖。
解決方案
- matplotlib 與 tkinter 集成方案
使用FigureCanvasTkAgg
嵌入圖表:python
運行
import tkinter as tk from matplotlib.backends.backend_tkagg import FigureCanvasTkAggdef create_tk_chart(root, data):fig, ax = plt.subplots(figsize=(8, 6))if not data.empty:ax.plot(data['x'], data['y'])else:ax.text(0.5, 0.5, "無數據", ha='center', va='center')ax.set_title("數據圖表")canvas = FigureCanvasTkAgg(fig, master=root)canvas.draw()canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)return canvas
- 數據路徑與權限檢查
- 使用絕對路徑讀取文件:
os.path.abspath("data.xlsx")
- 驗證文件權限:
os.access(file_path, os.R_OK)
- 示例錯誤處理:
python
運行
if not os.path.exists(file_path):raise FileNotFoundError(f"文件不存在: {file_path}")
- 使用絕對路徑讀取文件:
六、綜合調試流程建議
- 最小可行示例(MVE)驗證
- 先實現不依賴 UI 的純數據繪圖函數,確保核心邏輯正確。
- 再逐步集成 UI 組件,每次添加一個功能(如按鈕、數據輸入框)。
- 日志與調試工具
- 在關鍵節點添加
print
日志:print(f"當前數據形狀: {data.shape}")
- 使用
pdb
調試器:import pdb; pdb.set_trace()
- 在關鍵節點添加
- 版本控制與回滾
- 每次修改前通過
git commit
保存進度,便于回滾到穩定版本。
- 每次修改前通過
七、歷史問題復盤與預防
問題類型 | 之前踩的坑 | 預防措施 |
---|---|---|
環境配置 | .condarc 鏡像失效 | 統一使用pip config 配置鏡像 |
圖表顯示 | 未添加 % matplotlib inline | 在 Notebook 開頭固定添加魔術指令 |
數據空值 | 讀取數據后未校驗空值 | 繪圖前強制檢查data.empty |
交互邏輯 | 按鈕事件未正確觸發數據更新 | 采用分層事件處理架構,分離數據與視圖 |
tkinter 集成 | 圖表空白但數據存在 | 使用FigureCanvasTkAgg 正確嵌入圖表 |
通過以上方案,可系統性解決環境、數據、交互等層面的問題,確保可視化功能穩定運行。建議按 “數據校驗→核心繪圖→UI 集成” 的順序逐步調試,每次聚焦一個模塊的問題。
4 成長小結
項目答辯:踩坑成長與代碼改進之路
一、開場:從問題出發的成長敘事
各位評委老師好!今天我想通過 "踩坑 - 反思 - 改進" 的真實歷程,分享我在開發證券數據可視化系統中的技術成長。項目最初只是一個簡單的圖表繪制腳本,卻在實際應用中不斷遇到挑戰 —— 從環境配置失敗到交互邏輯混亂,從數據可視化空白到多源數據處理難題。每一個坑都倒逼我重新思考代碼設計,最終形成了現在這套更健壯的分析系統。下面我將以 "問題導向" 的方式,呈現這段改進之路。
二、第一階段:環境與基礎功能的坑(代碼 1 雛形)
1. 坑 1: matplotlib 換源配置反復失敗
- 踩坑經歷:
最初按教程修改.condarc
文件配置鏡像,以為配置成功但安裝庫時仍報錯。花了 3 小時排查才發現:- Anaconda 鏡像優先級高于 pip,導致
.condarc
配置失效 - 不同環境(Jupyter / 終端)的鏡像配置可能沖突
- Anaconda 鏡像優先級高于 pip,導致
- 改進代碼:
放棄.condarc
,改用 pip 永久換源:bash
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
- 成長收獲:
認識到環境配置需遵循 "最小依賴" 原則,pip 換源比 conda 更直接可靠。
2. 坑 2:Jupyter 圖表不顯示之謎
- 踩坑經歷:
寫好的繪圖代碼在 Jupyter 中運行只輸出空白,檢查發現:- 未添加
%matplotlib inline
魔術指令 - 中文字體未配置導致圖表渲染失敗
- 未添加
- 改進代碼:
在 Notebook 開頭固定添加:python
運行
%matplotlib inline plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei"] plt.rcParams["axes.unicode_minus"] = False
- 成長收獲:
總結出 Jupyter 繪圖 "三板斧":魔術指令 + 字體配置 + 錯誤捕獲。
三、第二階段:數據處理與可視化的坑(代碼 1 優化)
1. 坑 3:交互式圖表數據空白
- 踩坑經歷:
點擊刷新按鈕后圖表空白,但單獨測試數據讀取和繪圖正常,定位到:- 交互事件觸發時未正確傳遞最新數據
- 數據中存在空值未處理
- 繪圖前未校驗數據有效性
- 改進代碼:
添加數據校驗函數:python
運行
def validate_data(data):if data.empty:raise ValueError("數據為空,無法繪圖")na_count = data.isna().sum().sum()if na_count > 0:print(f"警告:數據包含{na_count}個空值,已過濾")return data.dropna()return data
在繪圖前強制調用:python
運行
try:valid_data = validate_data(user_data)plot_chart(valid_data) except ValueError as e:show_warning(e)
- 成長收獲:
建立 "數據校驗 - 異常處理 - 錯誤可視化" 的完整流程,避免 "黑盒" 錯誤。
2. 坑 4:圖表類型與數據不匹配
- 踩坑經歷:
用折線圖繪制分類數據時圖表混亂,發現:- 未根據數據類型自動選擇圖表類型
- 柱狀圖 / 折線圖的參數配置混用
- 改進代碼:
添加智能圖表類型判斷:python
運行
def auto_select_chart(data, x_col, y_col):if is_numeric(data[y_col]) and is_time_series(data[x_col]):return "line" # 時間序列折線圖elif is_categorical(data[x_col]):return "bar" # 分類數據柱狀圖else:return "scatter"
- 成長收獲:
認識到數據可視化需 "量體裁衣",建立數據類型與圖表類型的映射規則。
四、第三階段:架構與工程化的坑(轉向代碼 2)
1. 坑 5:交互式界面與業務邏輯耦合
- 踩坑經歷:
代碼 1 的EnhancedExcelVisualizer
類中,UI 組件創建與數據處理邏輯混雜:- 修改一個按鈕事件需牽動整個類
- 新增圖表類型時需重寫
refresh_chart
方法 - 難以復用數據處理邏輯到其他場景
- 改進方案:
轉向代碼 2 的函數式架構,將系統拆分為:python
運行
# 模塊化拆分示例 data = load_data() # 數據加載 explored = explore_data(data) # 分析邏輯 visualize_data(explored) # 可視化
- 成長收獲:
理解 "關注點分離" 原則,函數式架構更適合業務邏輯的擴展與復用。
2. 坑 6:多源數據處理效率低下
- 踩坑經歷:
最初只能處理單個 Excel 文件,擴展到多文件時:- 重復代碼量大(每個文件寫一次讀取邏輯)
- 數據合并時列名不統一導致錯誤
- 無法追蹤數據來源
- 改進代碼:
實現批量加載與標準化處理:python
運行
def load_multi_files(file_paths):all_data = []for path in file_paths:try:df = pd.read_excel(path)# 標準化處理df['source_file'] = os.path.basename(path)df['trade_date'] = pd.to_datetime(df['trade_date'])all_data.append(df)except Exception as e:print(f"加載{path}失敗: {e}")return pd.concat(all_data, ignore_index=True)
- 成長收獲:
掌握批量數據處理模式,通過添加元數據(source_file
)提升可追溯性。
五、答辯現場:坑的復現與解決方案演示
1. 現場復現:數據空值導致圖表空白
- 演示步驟:
- 故意加載一個空數據文件
- 點擊刷新按鈕,圖表顯示 "數據為空" 提示
- 查看日志:
validate_data
函數捕獲到空值異常
- 解決方案代碼:
python
運行
def plot_with_error_handling(data):try:valid_data = validate_data(data)# 正常繪圖邏輯except ValueError:plt.text(0.5, 0.5, "無有效數據", ha='center', va='center')plt.axis('off')
2. 現場演示:多文件加載與異常處理
- 演示內容:
- 同時加載 3 個文件,其中 1 個文件格式錯誤
- 系統自動跳過錯誤文件,合并正常數據
- 日志顯示:
加載output3.xlsx失敗: 列名不匹配
- 關鍵代碼展示:
python
運行
# 錯誤捕獲與跳過 except Exception as e:print(f"加載{path}失敗: {e}")continue # 跳過當前文件
六、評委提問:從踩坑到成長的深度思考
1. 提問:如何避免重復踩相同的坑?
- 回答:
我建立了 "坑記錄 - 復盤 - 預防" 機制:- 每個坑都記錄在 README 的 TROUBLESHOOTING 章節
- 復盤時提煉成可復用的代碼片段(如數據校驗函數)
- 新功能開發前先查閱歷史坑記錄,提前預防
(展示項目中的.pitfalls.md
文件,包含 12 個歷史坑的解決方案)
2. 提問:踩坑過程中最大的技術認知轉變是什么?
- 回答:
最初認為 "代碼能跑就行",但在踩過環境配置、數據空值等坑后,認識到:- 工程化代碼需要 "防御性編程"(處處校驗輸入)
- 系統設計要 "面向變化"(如函數式架構比類更靈活)
- 錯誤處理不是 "附加功能",而是核心邏輯的一部分
七、總結:坑是成長的階梯
從最初連圖表都畫不出來,到現在能處理多源數據并生成系統化分析報告,每一個坑都成為了成長的階梯。這段經歷讓我深刻理解:
- 技術成長 = 解決問題的積累:不是代碼量的增加,而是處理異常場景能力的提升
- 好代碼是改出來的:從代碼 1 到代碼 2 的改進,不是推倒重來,而是每次踩坑后的針對性優化
- 坑的價值在于被轉化:將所有踩過的坑轉化為代碼中的校驗邏輯、異常處理和文檔說明,才是真正的成長
未來我將繼續保持 "遇坑 - 析坑 - 填坑" 的習慣,讓每一次問題都成為技術提升的契機。謝謝!
八、答辯后改進計劃(基于坑的總結)
- 坑預防自動化:
開發pitfall_checker.py
腳本,自動掃描代碼中可能引發歷史坑的模式 - 坑文檔可視化:
將.pitfalls.md
轉化為交互式流程圖,直觀展示坑的觸發條件與解決方案 - 新人防坑指南:
為項目編寫《新人避坑手冊》,包含環境配置、數據處理等常見坑的一鍵解決方案