一、Pandas生态系统全景
"""Pandas生态系统层次:1. 核心:Series(一维) + DataFrame(二维)2. 基础:NumPy数组作为底层存储3. 扩展:时间序列、分类数据、稀疏数据4. 集成:Matplotlib可视化、SQL/Excel/JSON读写5. 高级:GroupBy、窗口函数、数据透视表Pandas设计哲学:1. 表格化数据操作2. 自动对齐数据3. 处理缺失数据4. 灵活的数据重塑5. 强大的时间序列支持"""import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsfrom datetime import datetime, timedeltaimport warningswarnings.filterwarnings('ignore')# 设置显示选项pd.set_option('display.max_rows', 100)pd.set_option('display.max_columns', 50)pd.set_option('display.width', 150)pd.set_option('display.float_format', '{:.2f}'.format)print(f"Pandas版本: {pd.__version__}")print(f"NumPy版本: {np.__version__}")# 检查Pandas配置print(f"\nPandas配置:")for key in ['display.max_rows', 'display.max_columns', 'display.width']: print(f" {key}: {pd.get_option(key)}")
二、Pandas核心数据结构
1. Series:带标签的一维数组
def demonstrate_series(): """演示Series数据结构""" print("=" * 80) print("Pandas Series 数据结构") print("=" * 80) # 1. 多种创建方式 print("\n1. Series创建方式:") # 从列表创建 s1 = pd.Series([10, 20, 30, 40, 50]) print(f"从列表创建: {s1}") # 从列表创建,指定索引 s2 = pd.Series([10, 20, 30, 40, 50], index=['A', 'B', 'C', 'D', 'E']) print(f"\n指定索引创建:\n{s2}") # 从字典创建 s3 = pd.Series({'Alice': 85, 'Bob': 92, 'Charlie': 78, 'David': 88}) print(f"\n从字典创建:\n{s3}") # 从NumPy数组创建 s4 = pd.Series(np.random.randn(5), index=pd.date_range('2024-01-01', periods=5)) print(f"\n从NumPy数组创建 (带日期索引):\n{s4}") # 2. Series属性 print("\n2. Series属性:") print(f"值: {s2.values}") print(f"索引: {s2.index}") print(f"数据类型: {s2.dtype}") print(f"形状: {s2.shape}") print(f"大小: {s2.size}") print(f"是否为空: {s2.empty}") print(f"维度: {s2.ndim}") # 3. Series索引 print("\n3. Series索引:") print(f"s2['B']: {s2['B']}") print(f"s2[['A', 'C', 'E']]:\n{s2[['A', 'C', 'E']]}") print(f"s2[1:4]:\n{s2[1:4]}") print(f"s2[s2 > 30]:\n{s2[s2 > 30]}") # 4. Series操作 print("\n4. Series操作:") # 向量化运算 s5 = s2 * 2 + 10 print(f"向量化运算 (s2 * 2 + 10):\n{s5}") # 统计运算 print(f"\n统计信息:") print(f" 总和: {s2.sum()}") print(f" 均值: {s2.mean():.2f}") print(f" 标准差: {s2.std():.2f}") print(f" 最小值: {s2.min()}") print(f" 最大值: {s2.max()}") print(f" 中位数: {s2.median()}") print(f" 分位数: {s2.quantile([0.25, 0.5, 0.75])}") # 5. Series方法 print("\n5. Series方法:") # 排序 print(f"排序 (升序):\n{s2.sort_values()}") print(f"排序 (降序):\n{s2.sort_values(ascending=False)}") # 排名 print(f"排名:\n{s2.rank()}") # 值计数 s6 = pd.Series(['A', 'B', 'A', 'C', 'B', 'A', 'D']) print(f"\n值计数:\n{s6.value_counts()}") # 唯一值 print(f"唯一值: {s6.unique()}") print(f"值计数 (归一化):\n{s6.value_counts(normalize=True)}") # 6. 处理缺失值 print("\n6. 缺失值处理:") s7 = pd.Series([1, 2, np.nan, 4, np.nan, 6]) print(f"原始Series: {s7}") print(f"是否有缺失值: {s7.hasnans}") print(f"缺失值数量: {s7.isna().sum()}") print(f"非缺失值数量: {s7.notna().sum()}") # 填充缺失值 s7_filled = s7.fillna(s7.mean()) print(f"均值填充后: {s7_filled}") # 删除缺失值 s7_dropped = s7.dropna() print(f"删除缺失值后: {s7_dropped}") # 7. 索引操作 print("\n7. 索引操作:") # 重命名索引 s8 = s2.rename({'A': 'Alpha', 'B': 'Beta'}) print(f"重命名索引:\n{s8}") # 重置索引 s9 = s2.reset_index() print(f"重置索引 (返回DataFrame):\n{s9}") # 设置新索引 s10 = s2.set_axis(['X', 'Y', 'Z', 'W', 'V']) print(f"设置新索引:\n{s10}") # 8. 类型转换 print("\n8. 类型转换:") s11 = pd.Series(['1', '2', '3', '4', '5']) print(f"原始类型: {s11.dtype}") s11_numeric = pd.to_numeric(s11) print(f"转换后类型: {s11_numeric.dtype}") print(f"转换后值: {s11_numeric}") # 9. 时间序列Series print("\n9. 时间序列Series:") dates = pd.date_range('2024-01-01', periods=10, freq='D') ts = pd.Series(np.random.randn(10), index=dates) print(f"时间序列:\n{ts}") print(f"索引类型: {type(ts.index)}") print(f"日期范围: {ts.index.min()} 到 {ts.index.max()}") # 时间序列操作 print(f"按月份重采样 (取均值):\n{ts.resample('M').mean()}") return { 's1': s1, 's2': s2, 's3': s3, 's4': s4, 's6': s6, 's7': s7, 'ts': ts }# 执行Series演示series_data = demonstrate_series()
2. DataFrame:带标签的二维表格
def demonstrate_dataframe(): """演示DataFrame数据结构""" print("=" * 80) print("Pandas DataFrame 数据结构") print("=" * 80) # 1. DataFrame创建 print("\n1. DataFrame创建方式:") # 从字典创建 df1 = pd.DataFrame({ '姓名': ['张三', '李四', '王五', '赵六'], '年龄': [25, 30, 35, 28], '城市': ['北京', '上海', '广州', '深圳'], '薪资': [15000, 18000, 22000, 16000] }) print(f"从字典创建:\n{df1}") # 从列表创建 data = [ ['苹果', 5.5, 10], ['香蕉', 3.2, 20], ['橙子', 4.8, 15], ['葡萄', 6.5, 8] ] df2 = pd.DataFrame(data, columns=['水果', '价格', '库存']) print(f"\n从列表创建:\n{df2}") # 从NumPy数组创建 arr = np.random.randn(5, 4) df3 = pd.DataFrame(arr, columns=['A', 'B', 'C', 'D'], index=pd.date_range('2024-01-01', periods=5)) print(f"\n从NumPy数组创建:\n{df3}") # 从Series创建 df4 = pd.DataFrame({ '成绩': pd.Series([85, 92, 78, 88], index=['A', 'B', 'C', 'D']), '等级': pd.Series(['A', 'A', 'B', 'B'], index=['A', 'B', 'C', 'D']) }) print(f"\n从Series创建:\n{df4}") # 2. DataFrame属性 print("\n2. DataFrame属性:") print(f"形状: {df1.shape}") print(f"维度: {df1.ndim}") print(f"大小: {df1.size}") print(f"列名: {df1.columns.tolist()}") print(f"索引: {df1.index.tolist()}") print(f"数据类型:\n{df1.dtypes}") print(f"内存使用: {df1.memory_usage().sum()} bytes") # 3. 数据查看 print("\n3. 数据查看方法:") print(f"前2行:\n{df1.head(2)}") print(f"后2行:\n{df1.tail(2)}") print(f"随机3行:\n{df1.sample(3, random_state=42)}") print(f"基本信息:\n{df1.info()}") print(f"描述性统计:\n{df1.describe()}") print(f"详细描述 (包含所有列):\n{df1.describe(include='all')}") # 4. 数据选择 print("\n4. 数据选择:") # 选择列 print(f"选择单列 (df1['姓名']):\n{df1['姓名']}") print(f"选择多列 (df1[['姓名', '薪资']]):\n{df1[['姓名', '薪资']]}") # 选择行 print(f"\n选择单行 (df1.iloc[1]):\n{df1.iloc[1]}") print(f"选择多行 (df1.iloc[1:3]):\n{df1.iloc[1:3]}") # loc索引器 print(f"\nloc选择 (df1.loc[1:2, ['姓名', '年龄']]):\n{df1.loc[1:2, ['姓名', '年龄']]}") # iloc索引器 print(f"iloc选择 (df1.iloc[0:2, 0:2]):\n{df1.iloc[0:2, 0:2]}") # 条件选择 print(f"\n条件选择 (df1[df1['薪资'] > 17000]):\n{df1[df1['薪资'] > 17000]}") print(f"多条件选择 (df1[(df1['年龄'] > 28) & (df1['薪资'] > 16000)]):") print(df1[(df1['年龄'] > 28) & (df1['薪资'] > 16000)]) # 5. 数据操作 print("\n5. 数据操作:") # 添加新列 df1['薪资等级'] = np.where(df1['薪资'] > 17000, '高', '中') print(f"添加新列:\n{df1}") # 修改列 df1['年龄加1'] = df1['年龄'] + 1 print(f"\n修改列:\n{df1}") # 删除列 df1_dropped = df1.drop(columns=['年龄加1']) print(f"删除列:\n{df1_dropped}") # 重命名列 df1_renamed = df1.rename(columns={'姓名': 'Name', '年龄': 'Age'}) print(f"重命名列:\n{df1_renamed}") # 6. 数据处理 print("\n6. 数据处理:") # 缺失值处理 df5 = pd.DataFrame({ 'A': [1, 2, np.nan, 4], 'B': [5, np.nan, np.nan, 8], 'C': [9, 10, 11, 12] }) print(f"原始数据:\n{df5}") print(f"缺失值统计:\n{df5.isna().sum()}") print(f"填充缺失值 (向前填充):\n{df5.fillna(method='ffill')}") print(f"填充缺失值 (列均值):\n{df5.fillna(df5.mean())}") # 重复值处理 df6 = pd.DataFrame({ '姓名': ['张三', '李四', '张三', '王五', '李四'], '成绩': [85, 92, 85, 78, 92] }) print(f"\n重复数据:\n{df6}") print(f"重复行:\n{df6[df6.duplicated()]}") print(f"删除重复行:\n{df6.drop_duplicates()}") print(f"删除重复行 (基于姓名):\n{df6.drop_duplicates(subset=['姓名'])}") # 7. 数据转换 print("\n7. 数据转换:") # 数据类型转换 print(f"数据类型:\n{df1.dtypes}") df1['年龄'] = df1['年龄'].astype('float32') print(f"转换后类型:\n{df1.dtypes}") # 应用函数 df1['薪资千元'] = df1['薪资'].apply(lambda x: x/1000) print(f"\n应用函数:\n{df1[['薪资', '薪资千元']]}") # 向量化操作 df1['薪资加10%'] = df1['薪资'] * 1.1 print(f"向量化操作:\n{df1[['薪资', '薪资加10%']]}") # 8. 数据排序 print("\n8. 数据排序:") print(f"按薪资排序:\n{df1.sort_values('薪资')}") print(f"按薪资降序排序:\n{df1.sort_values('薪资', ascending=False)}") print(f"多列排序 (先按城市,再按薪资):") print(df1.sort_values(['城市', '薪资'], ascending=[True, False])) # 9. 数据分组 print("\n9. 数据分组:") # 简单分组 grouped = df1.groupby('城市') print(f"分组对象: {type(grouped)}") # 分组聚合 print(f"按城市分组统计:\n{grouped.agg({'薪资': ['mean', 'sum', 'count'], '年龄': 'mean'})}") # 分组转换 df1['城市平均薪资'] = grouped['薪资'].transform('mean') print(f"添加城市平均薪资:\n{df1}") # 10. 数据透视表 print("\n10. 数据透视表:") # 创建示例数据 sales_data = pd.DataFrame({ '日期': pd.date_range('2024-01-01', periods=30).tolist() * 3, '产品': ['A'] * 30 + ['B'] * 30 + ['C'] * 30, '类别': np.random.choice(['电子产品', '日用品', '服装'], 90), '销售额': np.random.randint(100, 1000, 90), '数量': np.random.randint(1, 10, 90) }) # 创建透视表 pivot = pd.pivot_table(sales_data, values='销售额', index='产品', columns='类别', aggfunc='sum', fill_value=0) print(f"销售额透视表:\n{pivot}") # 多级透视表 pivot_multi = pd.pivot_table(sales_data, values=['销售额', '数量'], index=['产品', '类别'], aggfunc={'销售额': 'sum', '数量': 'mean'}) print(f"\n多级透视表:\n{pivot_multi}") return { 'df1': df1, 'df2': df2, 'df3': df3, 'df4': df4, 'df5': df5, 'df6': df6, 'sales_data': sales_data, 'pivot': pivot, 'pivot_multi': pivot_multi }# 执行DataFrame演示dataframes = demonstrate_dataframe()
三、数据导入与导出
def demonstrate_data_io(): """演示数据导入导出操作""" print("=" * 80) print("数据导入与导出") print("=" * 80) # 1. 创建示例数据 print("1. 创建示例数据") # 员工数据 employees = pd.DataFrame({ '员工ID': ['E001', 'E002', 'E003', 'E004', 'E005'], '姓名': ['张三', '李四', '王五', '赵六', '钱七'], '部门': ['技术部', '市场部', '技术部', '人事部', '市场部'], '入职日期': pd.date_range('2023-01-01', periods=5, freq='M'), '薪资': [15000, 18000, 22000, 16000, 19000], '绩效评分': [85, 92, 78, 88, 90] }) # 销售数据 sales = pd.DataFrame({ '订单ID': ['O001', 'O002', 'O003', 'O004', 'O005'], '员工ID': ['E001', 'E002', 'E001', 'E003', 'E004'], '产品': ['电脑', '手机', '平板', '电脑', '手机'], '数量': [2, 1, 3, 1, 2], '单价': [5000, 3000, 2000, 5000, 3000], '日期': pd.date_range('2024-01-01', periods=5, freq='D') }) print(f"员工数据:\n{employees}") print(f"\n销售数据:\n{sales}") # 2. 导出数据 print("\n2. 导出数据到不同格式:") # 导出到CSV employees.to_csv('employees.csv', index=False, encoding='utf-8-sig') sales.to_csv('sales.csv', index=False, encoding='utf-8-sig') # 导出到Excel with pd.ExcelWriter('company_data.xlsx') as writer: employees.to_excel(writer, sheet_name='员工信息', index=False) sales.to_excel(writer, sheet_name='销售记录', index=False) # 导出到JSON employees.to_json('employees.json', orient='records', force_ascii=False) # 导出到Parquet(高效列式存储) try: employees.to_parquet('employees.parquet') print("已导出到Parquet格式") except ImportError: print("安装pyarrow以支持Parquet格式: pip install pyarrow") # 导出到SQL数据库 import sqlite3 # 创建内存数据库 conn = sqlite3.connect(':memory:') employees.to_sql('employees', conn, if_exists='replace', index=False) sales.to_sql('sales', conn, if_exists='replace', index=False) print("\n已导出到SQLite数据库") # 3. 导入数据 print("\n3. 从不同格式导入数据:") # 从CSV导入 employees_csv = pd.read_csv('employees.csv', encoding='utf-8-sig') print(f"从CSV导入:\n{employees_csv.head()}") # 从Excel导入 employees_excel = pd.read_excel('company_data.xlsx', sheet_name='员工信息') sales_excel = pd.read_excel('company_data.xlsx', sheet_name='销售记录') print(f"\n从Excel导入员工数据:\n{employees_excel.head()}") # 从JSON导入 employees_json = pd.read_json('employees.json') print(f"\n从JSON导入:\n{employees_json.head()}") # 从Parquet导入 try: employees_parquet = pd.read_parquet('employees.parquet') print(f"\n从Parquet导入:\n{employees_parquet.head()}") except ImportError: print("\n跳过Parquet导入测试") # 从SQL导入 employees_sql = pd.read_sql('SELECT * FROM employees', conn) sales_sql = pd.read_sql('SELECT * FROM sales', conn) print(f"\n从SQL导入员工数据:\n{employees_sql.head()}") # 4. 读取大型文件 print("\n4. 读取大型文件技巧:") # 分块读取 chunk_size = 2 chunks = [] for chunk in pd.read_csv('employees.csv', chunksize=chunk_size, encoding='utf-8-sig'): chunks.append(chunk) print(f"读取块 {len(chunks)}:\n{chunk}") # 合并块 if chunks: large_df = pd.concat(chunks, ignore_index=True) print(f"\n合并后的数据:\n{large_df}") # 5. 数据预览 print("\n5. 数据预览方法:") # 查看文件信息 import os print(f"文件大小: {os.path.getsize('employees.csv')} bytes") # 只读取前几行 preview = pd.read_csv('employees.csv', nrows=3, encoding='utf-8-sig') print(f"预览前3行:\n{preview}") # 6. 数据类型优化 print("\n6. 数据类型优化:") # 查看内存使用 print(f"原始数据内存使用: {employees.memory_usage().sum()} bytes") # 优化数据类型 employees_optimized = employees.copy() # 转换整数类型 employees_optimized['员工ID'] = employees_optimized['员工ID'].astype('category') employees_optimized['姓名'] = employees_optimized['姓名'].astype('category') employees_optimized['部门'] = employees_optimized['部门'].astype('category') # 转换数值类型 employees_optimized['薪资'] = employees_optimized['薪资'].astype('int32') employees_optimized['绩效评分'] = employees_optimized['绩效评分'].astype('int8') print(f"优化后内存使用: {employees_optimized.memory_usage().sum()} bytes") print(f"内存节省: {(employees.memory_usage().sum() - employees_optimized.memory_usage().sum()) / employees.memory_usage().sum() * 100:.1f}%") # 7. 清理临时文件 import os for file in ['employees.csv', 'sales.csv', 'company_data.xlsx', 'employees.json', 'employees.parquet']: if os.path.exists(file): os.remove(file) print("\n已清理临时文件") return { 'employees': employees, 'sales': sales, 'employees_optimized': employees_optimized, 'conn': conn }# 执行数据IO演示io_data = demonstrate_data_io()
四、数据清洗与预处理
def demonstrate_data_cleaning(): """演示数据清洗与预处理""" print("=" * 80) print("数据清洗与预处理") print("=" * 80) # 1. 创建脏数据 print("1. 创建包含问题的数据集") # 创建有问题的数据 dirty_data = pd.DataFrame({ '客户ID': ['C001', 'C002', 'C003', 'C004', 'C005', 'C001', 'C006', 'C007', None, 'C009'], '姓名': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Alice', 'Frank', 'Grace', 'Henry', 'Ivy'], '年龄': [25, 30, 35, 28, 22, 25, 45, 32, 29, 31], '城市': ['北京', '上海', '广州', '深圳', '北京', '北京', '上海', '广州', '深圳', None], '消费金额': [1500.50, 2800.00, 1200.75, 3500.25, 800.00, 1500.50, 4200.50, 1900.00, 1300.75, 2600.00], '消费日期': ['2024-01-15', '2024-01-16', '2024-01-16', '2024-01-17', '2024-01-17', '2024-01-15', '2024-01-18', '2024-01-18', '2024-01-19', '2024-01-20'], '会员等级': ['Gold', 'Silver', 'Bronze', 'Gold', 'Silver', 'Gold', 'Platinum', 'Silver', 'Bronze', 'Gold'], '联系电话': ['13800138000', '13900139000', '13700137000', '13600136000', '13500135000', '13800138000', '13400134000', '13300133000', '13200132000', '13100131000'] }) # 添加更多问题 dirty_data.loc[2, '年龄'] = 350 # 异常值 dirty_data.loc[4, '消费金额'] = -800 # 负值 dirty_data.loc[7, '年龄'] = None # 缺失值 dirty_data.loc[9, '会员等级'] = 'Diamond' # 异常类别 print(f"原始数据 (包含各种问题):\n{dirty_data}") # 2. 识别问题 print("\n2. 识别数据问题:") # 缺失值分析 print("缺失值分析:") missing_info = pd.DataFrame({ '缺失数量': dirty_data.isna().sum(), '缺失比例': dirty_data.isna().sum() / len(dirty_data) * 100 }) print(missing_info) # 重复值分析 print(f"\n重复行数量: {dirty_data.duplicated().sum()}") print(f"重复行:\n{dirty_data[dirty_data.duplicated()]}") # 异常值分析 print("\n异常值分析:") numeric_cols = dirty_data.select_dtypes(include=[np.number]).columns for col in numeric_cols: Q1 = dirty_data[col].quantile(0.25) Q3 = dirty_data[col].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR outliers = dirty_data[(dirty_data[col] < lower_bound) | (dirty_data[col] > upper_bound)] print(f"{col}: {len(outliers)} 个异常值") # 3. 处理缺失值 print("\n3. 处理缺失值:") cleaned_data = dirty_data.copy() # 删除客户ID缺失的行 cleaned_data = cleaned_data.dropna(subset=['客户ID']) print(f"删除客户ID缺失的行后: {len(cleaned_data)} 行") # 填充城市缺失值 cleaned_data['城市'] = cleaned_data['城市'].fillna('未知') print("城市缺失值已填充为'未知'") # 4. 处理重复值 print("\n4. 处理重复值:") # 删除完全重复的行 cleaned_data = cleaned_data.drop_duplicates() print(f"删除完全重复行后: {len(cleaned_data)} 行") # 5. 处理异常值 print("\n5. 处理异常值:") # 处理年龄异常值 (350岁不合理) age_median = cleaned_data['年龄'].median() cleaned_data['年龄'] = cleaned_data['年龄'].apply( lambda x: age_median if pd.notna(x) and x > 120 else x ) print(f"年龄异常值已用中位数 ({age_median}) 替换") # 处理消费金额负值 cleaned_data['消费金额'] = cleaned_data['消费金额'].abs() print("消费金额负值已转换为正值") # 6. 数据类型转换 print("\n6. 数据类型转换:") # 转换消费日期为datetime cleaned_data['消费日期'] = pd.to_datetime(cleaned_data['消费日期']) print(f"消费日期已转换为datetime类型: {cleaned_data['消费日期'].dtype}") # 转换会员等级为分类类型 valid_categories = ['Bronze', 'Silver', 'Gold', 'Platinum'] cleaned_data['会员等级'] = pd.Categorical( cleaned_data['会员等级'], categories=valid_categories, ordered=True ) print(f"会员等级已转换为分类类型: {cleaned_data['会员等级'].dtype}") # 7. 特征工程 print("\n7. 特征工程:") # 提取日期特征 cleaned_data['消费年份'] = cleaned_data['消费日期'].dt.year cleaned_data['消费月份'] = cleaned_data['消费日期'].dt.month cleaned_data['消费日'] = cleaned_data['消费日期'].dt.day cleaned_data['消费星期'] = cleaned_data['消费日期'].dt.day_name() # 创建客户年龄分段 bins = [0, 25, 35, 50, 100] labels = ['青年', '中青年', '中年', '中老年'] cleaned_data['年龄分段'] = pd.cut(cleaned_data['年龄'], bins=bins, labels=labels) # 创建消费金额分段 amount_bins = [0, 1000, 2000, 3000, 5000] amount_labels = ['低', '中', '高', '很高'] cleaned_data['消费等级'] = pd.cut(cleaned_data['消费金额'], bins=amount_bins, labels=amount_labels) # 8. 数据标准化/归一化 print("\n8. 数据标准化:") from sklearn.preprocessing import StandardScaler, MinMaxScaler # Z-score标准化 scaler_z = StandardScaler() cleaned_data['消费金额_zscore'] = scaler_z.fit_transform( cleaned_data[['消费金额']] ) # Min-Max归一化 scaler_mm = MinMaxScaler() cleaned_data['消费金额_minmax'] = scaler_mm.fit_transform( cleaned_data[['消费金额']] ) # 9. 处理分类变量 print("\n9. 处理分类变量:") # One-Hot编码 city_dummies = pd.get_dummies(cleaned_data['城市'], prefix='城市') print(f"城市One-Hot编码:\n{city_dummies.head()}") # 标签编码 from sklearn.preprocessing import LabelEncoder le = LabelEncoder() cleaned_data['会员等级编码'] = le.fit_transform(cleaned_data['会员等级']) print(f"会员等级标签编码: {dict(zip(le.classes_, le.transform(le.classes_)))}") # 10. 数据验证 print("\n10. 数据验证:") print("清洗后数据信息:") print(cleaned_data.info()) print("\n清洗后数据描述性统计:") print(cleaned_data.describe(include='all')) print("\n清洗后缺失值统计:") print(cleaned_data.isna().sum()) print("\n清洗后重复行统计:") print(cleaned_data.duplicated().sum()) # 可视化清洗效果 fig, axes = plt.subplots(2, 2, figsize=(12, 10)) # 1. 缺失值对比 axes[0, 0].bar(missing_info.index, missing_info['缺失比例']) axes[0, 0].set_title('原始数据缺失值比例') axes[0, 0].set_ylabel('缺失比例 (%)') axes[0, 0].tick_params(axis='x', rotation=45) # 2. 年龄分布对比 axes[0, 1].hist(dirty_data['年龄'].dropna(), bins=10, alpha=0.5, label='原始', color='red') axes[0, 1].hist(cleaned_data['年龄'].dropna(), bins=10, alpha=0.5, label='清洗后', color='blue') axes[0, 1].set_title('年龄分布对比') axes[0, 1].set_xlabel('年龄') axes[0, 1].set_ylabel('频数') axes[0, 1].legend() # 3. 消费金额箱线图对比 amount_data = [dirty_data['消费金额'].dropna(), cleaned_data['消费金额']] axes[1, 0].boxplot(amount_data, labels=['原始', '清洗后']) axes[1, 0].set_title('消费金额分布对比') axes[1, 0].set_ylabel('消费金额') # 4. 会员等级分布 member_counts = cleaned_data['会员等级'].value_counts().sort_index() axes[1, 1].bar(member_counts.index.astype(str), member_counts.values) axes[1, 1].set_title('清洗后会员等级分布') axes[1, 1].set_xlabel('会员等级') axes[1, 1].set_ylabel('客户数') plt.suptitle('数据清洗效果可视化', fontsize=16, y=1.02) plt.tight_layout() plt.show() return { 'dirty_data': dirty_data, 'cleaned_data': cleaned_data, 'city_dummies': city_dummies, 'missing_info': missing_info }# 执行数据清洗演示cleaning_data = demonstrate_data_cleaning()
五、数据聚合与分析
def demonstrate_data_aggregation(): """演示数据聚合与分析""" print("=" * 80) print("数据聚合与分析") print("=" * 80) # 1. 创建销售数据集 print("1. 创建销售数据集") np.random.seed(42) n_records = 1000 dates = pd.date_range('2024-01-01', periods=90, freq='D') products = ['手机', '电脑', '平板', '耳机', '手表'] regions = ['华东', '华南', '华北', '华中', '西南', '西北'] categories = ['电子产品', '配件'] sales_data = pd.DataFrame({ '订单ID': [f'ORD{i:05d}' for i in range(1, n_records + 1)], '日期': np.random.choice(dates, n_records), '产品': np.random.choice(products, n_records, p=[0.3, 0.25, 0.2, 0.15, 0.1]), '类别': np.random.choice(categories, n_records, p=[0.7, 0.3]), '地区': np.random.choice(regions, n_records), '销售员ID': [f'SAL{np.random.randint(1, 21):03d}' for _ in range(n_records)], '数量': np.random.randint(1, 6, n_records), '单价': np.random.choice([2999, 5999, 1999, 399, 1299], n_records), '折扣': np.random.uniform(0.9, 1.0, n_records) # 9-10折 }) # 计算金额 sales_data['金额'] = sales_data['数量'] * sales_data['单价'] * sales_data['折扣'] # 添加客户评分 sales_data['评分'] = np.random.randint(1, 6, n_records) print(f"销售数据形状: {sales_data.shape}") print(f"\n数据预览:\n{sales_data.head()}") # 2. 基本聚合 print("\n2. 基本聚合计算:") print(f"总销售额: {sales_data['金额'].sum():,.2f}") print(f"平均销售额: {sales_data['金额'].mean():,.2f}") print(f"总订单数: {sales_data['订单ID'].nunique()}") print(f"平均订单金额: {sales_data['金额'].sum() / sales_data['订单ID'].nunique():,.2f}") # 3. GroupBy聚合 print("\n3. GroupBy聚合:") # 按地区分组 region_stats = sales_data.groupby('地区').agg({ '订单ID': 'count', '金额': ['sum', 'mean', 'std'], '评分': 'mean' }).round(2) region_stats.columns = ['订单数', '总金额', '平均金额', '金额标准差', '平均评分'] print(f"按地区统计:\n{region_stats}") # 4. 多级分组 print("\n4. 多级分组:") product_region_stats = sales_data.groupby(['产品', '地区']).agg({ '订单ID': 'count', '金额': 'sum', '评分': 'mean' }).round(2) print(f"按产品和地区统计 (前10行):\n{product_region_stats.head(10)}") # 5. 透视表 print("\n5. 透视表分析:") # 销售额透视表 pivot_sales = pd.pivot_table( sales_data, values='金额', index='产品', columns='地区', aggfunc='sum', fill_value=0, margins=True, # 添加总计 margins_name='总计' ) print(f"销售额透视表:\n{pivot_sales}") # 多指标透视表 pivot_multi = pd.pivot_table( sales_data, values=['金额', '数量', '评分'], index=['产品', '类别'], aggfunc={'金额': 'sum', '数量': 'sum', '评分': 'mean'} ).round(2) print(f"\n多指标透视表:\n{pivot_multi}") # 6. 交叉表 print("\n6. 交叉表分析:") # 产品和类别的交叉表 cross_tab = pd.crosstab( sales_data['产品'], sales_data['类别'], margins=True, margins_name='总计' ) print(f"产品×类别交叉表:\n{cross_tab}") # 带数值的交叉表 cross_tab_value = pd.crosstab( sales_data['产品'], sales_data['类别'], values=sales_data['金额'], aggfunc='sum', margins=True, margins_name='总计' ).round(2) print(f"\n产品×类别金额交叉表:\n{cross_tab_value}") # 7. 时间序列聚合 print("\n7. 时间序列聚合:") # 按日统计 daily_sales = sales_data.groupby(pd.Grouper(key='日期', freq='D')).agg({ '订单ID': 'count', '金额': 'sum' }).rename(columns={'订单ID': '订单数', '金额': '日销售额'}) # 按周统计 weekly_sales = sales_data.groupby(pd.Grouper(key='日期', freq='W')).agg({ '订单ID': 'count', '金额': 'sum' }).rename(columns={'订单ID': '订单数', '金额': '周销售额'}) # 按月统计 monthly_sales = sales_data.groupby(pd.Grouper(key='日期', freq='M')).agg({ '订单ID': 'count', '金额': 'sum', '评分': 'mean' }).rename(columns={'订单ID': '订单数', '金额': '月销售额', '评分': '平均评分'}) print(f"月度销售统计:\n{monthly_sales}") # 8. 窗口函数 print("\n8. 窗口函数:") # 创建时间序列示例 ts_data = daily_sales['日销售额'].copy() # 移动平均 ts_data['MA_7'] = ts_data.rolling(window=7).mean() # 7日移动平均 ts_data['MA_30'] = ts_data.rolling(window=30).mean() # 30日移动平均 # 扩展窗口统计 ts_data['累计销售额'] = ts_data.expanding().sum() ts_data['历史最高'] = ts_data.expanding().max() print(f"时间序列窗口统计 (前10行):\n{ts_data.head(10)}") # 9. 排名和分位数 print("\n9. 排名和分位数:") # 销售员排名 salesperson_stats = sales_data.groupby('销售员ID').agg({ '订单ID': 'count', '金额': 'sum' }).rename(columns={'订单ID': '订单数', '金额': '总销售额'}) salesperson_stats['销售额排名'] = salesperson_stats['总销售额'].rank(ascending=False, method='min') salesperson_stats['订单数排名'] = salesperson_stats['订单数'].rank(ascending=False, method='min') # 分位数分析 salesperson_stats['销售额分位数'] = pd.qcut( salesperson_stats['总销售额'], q=4, labels=['低', '中下', '中上', '高'] ) print(f"销售员排名分析:\n{salesperson_stats.sort_values('销售额排名').head()}") # 10. 高级聚合技巧 print("\n10. 高级聚合技巧:") # 自定义聚合函数 def iqr(x): """计算四分位距""" return x.quantile(0.75) - x.quantile(0.25) def cv(x): """计算变异系数""" return x.std() / x.mean() if x.mean() != 0 else 0 # 应用自定义函数 advanced_stats = sales_data.groupby('产品').agg({ '金额': ['sum', 'mean', 'std', iqr, cv], '数量': ['sum', 'mean'], '评分': ['mean', 'std', lambda x: x.mode()[0] if not x.mode().empty else np.nan] }).round(2) # 重命名列 advanced_stats.columns = [ '总金额', '平均金额', '金额标准差', '金额IQR', '金额CV', '总数量', '平均数量', '平均评分', '评分标准差', '评分众数' ] print(f"高级统计:\n{advanced_stats}") # 可视化分析结果 fig, axes = plt.subplots(2, 2, figsize=(15, 10)) # 1. 地区销售额柱状图 region_stats['总金额'].plot(kind='bar', ax=axes[0, 0], color='steelblue') axes[0, 0].set_title('各地区总销售额') axes[0, 0].set_xlabel('地区') axes[0, 0].set_ylabel('销售额') axes[0, 0].tick_params(axis='x', rotation=45) # 2. 产品销售额堆叠柱状图 product_region_pivot = sales_data.pivot_table( values='金额', index='产品', columns='地区', aggfunc='sum' ) product_region_pivot.plot(kind='bar', stacked=True, ax=axes[0, 1]) axes[0, 1].set_title('各产品在不同地区的销售额') axes[0, 1].set_xlabel('产品') axes[0, 1].set_ylabel('销售额') axes[0, 1].legend(title='地区', bbox_to_anchor=(1.05, 1)) # 3. 时间序列图 axes[1, 0].plot(daily_sales.index, daily_sales['日销售额'], label='日销售额', alpha=0.7) axes[1, 0].plot(weekly_sales.index, weekly_sales['周销售额'], label='周销售额', linewidth=2) axes[1, 0].set_title('销售额时间序列') axes[1, 0].set_xlabel('日期') axes[1, 0].set_ylabel('销售额') axes[1, 0].legend() axes[1, 0].grid(True, alpha=0.3) # 4. 散点图:数量 vs 金额 axes[1, 1].scatter(sales_data['数量'], sales_data['金额'], alpha=0.5) axes[1, 1].set_title('数量 vs 金额') axes[1, 1].set_xlabel('数量') axes[1, 1].set_ylabel('金额') axes[1, 1].grid(True, alpha=0.3) plt.suptitle('销售数据分析可视化', fontsize=16, y=1.02) plt.tight_layout() plt.show() return { 'sales_data': sales_data, 'region_stats': region_stats, 'daily_sales': daily_sales, 'weekly_sales': weekly_sales, 'monthly_sales': monthly_sales, 'salesperson_stats': salesperson_stats, 'advanced_stats': advanced_stats }# 执行数据聚合演示aggregation_data = demonstrate_data_aggregation()
六、时间序列分析
def demonstrate_time_series(): """演示时间序列分析""" print("=" * 80) print("Pandas时间序列分析") print("=" * 80) # 1. 创建时间序列数据 print("1. 创建时间序列数据") np.random.seed(42) # 生成日期范围 date_range = pd.date_range('2023-01-01', '2023-12-31', freq='D') # 创建时间序列DataFrame time_series_data = pd.DataFrame({ '日期': date_range, '销售额': np.random.normal(10000, 2000, len(date_range)).cumsum() + 100000, '访问量': np.random.poisson(1000, len(date_range)), '转化率': np.random.beta(5, 2, len(date_range)) * 10, # 0-10% '客单价': np.random.normal(500, 50, len(date_range)) }) # 添加趋势和季节性 time_series_data['销售额'] = time_series_data['销售额'] * ( 1 + 0.001 * np.arange(len(date_range)) + # 趋势 0.1 * np.sin(2 * np.pi * np.arange(len(date_range)) / 365) # 年季节性 ) # 添加周季节性 time_series_data['访问量'] = time_series_data['访问量'] * ( 1 + 0.2 * np.sin(2 * np.pi * time_series_data['日期'].dt.dayofweek / 7) ) # 设置日期为索引 time_series_data.set_index('日期', inplace=True) print(f"时间序列数据形状: {time_series_data.shape}") print(f"\n数据预览:\n{time_series_data.head()}") # 2. 时间序列基础操作 print("\n2. 时间序列基础操作:") # 时间索引属性 print(f"索引类型: {type(time_series_data.index)}") print(f"时间范围: {time_series_data.index.min()} 到 {time_series_data.index.max()}") print(f"时间跨度: {time_series_data.index.max() - time_series_data.index.min()}") # 按时间选择数据 print(f"\n选择2023年1月数据:\n{time_series_data.loc['2023-01'].head()}") print(f"\n选择2023年第一季度数据:\n{time_series_data.loc['2023-Q1'].head()}") print(f"\n选择特定日期范围:\n{time_series_data.loc['2023-01-15':'2023-01-20']}") # 3. 时间频率转换 print("\n3. 时间频率转换:") # 按日统计 print("原始数据 (日频)") # 按周重采样 weekly_data = time_series_data.resample('W').agg({ '销售额': 'sum', '访问量': 'sum', '转化率': 'mean', '客单价': 'mean' }) print(f"\n周度数据:\n{weekly_data.head()}") # 按月重采样 monthly_data = time_series_data.resample('M').agg({ '销售额': ['sum', 'mean', 'std'], '访问量': ['sum', 'mean'], '转化率': 'mean', '客单价': 'mean' }) monthly_data.columns = ['销售额_总和', '销售额_均值', '销售额_标准差', '访问量_总和', '访问量_均值', '转化率_均值', '客单价_均值'] print(f"\n月度数据:\n{monthly_data.head()}") # 4. 时间序列分解 print("\n4. 时间序列分解:") # 使用statsmodels进行分解 try: from statsmodels.tsa.seasonal import seasonal_decompose # 使用加法模型分解销售额 result = seasonal_decompose( time_series_data['销售额'], model='additive', period=30 # 月度周期 ) print("时间序列分解完成 (加法模型)") print(f" 趋势成分: {len(result.trend)} 个点") print(f" 季节成分: {len(result.seasonal)} 个点") print(f" 残差成分: {len(result.resid)} 个点") except ImportError: print("安装statsmodels以进行时间序列分解: pip install statsmodels") result = None # 5. 移动窗口统计 print("\n5. 移动窗口统计:") # 移动平均 time_series_data['销售额_MA7'] = time_series_data['销售额'].rolling(window=7).mean() time_series_data['销售额_MA30'] = time_series_data['销售额'].rolling(window=30).mean() # 移动标准差 time_series_data['销售额_Std7'] = time_series_data['销售额'].rolling(window=7).std() # 扩展窗口统计 time_series_data['销售额_累计'] = time_series_data['销售额'].expanding().sum() time_series_data['销售额_历史最高'] = time_series_data['销售额'].expanding().max() print(f"移动窗口统计 (前10行):\n{time_series_data[['销售额', '销售额_MA7', '销售额_MA30']].head(10)}") # 6. 时间差计算 print("\n6. 时间差计算:") # 计算日环比 time_series_data['销售额_日环比'] = time_series_data['销售额'].pct_change(periods=1) # 计算周同比 time_series_data['销售额_周同比'] = time_series_data['销售额'].pct_change(periods=7) # 计算月同比 time_series_data['销售额_月同比'] = time_series_data['销售额'].pct_change(periods=30) print(f"变化率统计:\n{time_series_data[['销售额_日环比', '销售额_周同比', '销售额_月同比']].head(10)}") # 7. 时间序列特征提取 print("\n7. 时间序列特征提取:") # 提取日期特征 time_series_data['年份'] = time_series_data.index.year time_series_data['月份'] = time_series_data.index.month time_series_data['季度'] = time_series_data.index.quarter time_series_data['星期'] = time_series_data.index.dayofweek time_series_data['是否周末'] = time_series_data.index.dayofweek >= 5 time_series_data['是否月初'] = time_series_data.index.day <= 7 time_series_data['是否月末'] = time_series_data.index.day >= 23 # 提取滚动特征 for window in [3, 7, 14, 30]: time_series_data[f'销售额_MA{window}'] = time_series_data['销售额'].rolling(window=window).mean() time_series_data[f'销售额_Std{window}'] = time_series_data['销售额'].rolling(window=window).std() time_series_data[f'销售额_Min{window}'] = time_series_data['销售额'].rolling(window=window).min() time_series_data[f'销售额_Max{window}'] = time_series_data['销售额'].rolling(window=window).max() print(f"特征提取后数据形状: {time_series_data.shape}") print(f"特征列:\n{list(time_series_data.columns[:15])}...") # 8. 时间序列预测 print("\n8. 时间序列预测 (简单方法):") # 使用简单移动平均预测 def moving_average_forecast(series, window_size, forecast_horizon): """移动平均预测""" forecast = [] for i in range(forecast_horizon): # 使用最近window_size个值的平均值作为预测 pred = series.iloc[-window_size:].mean() forecast.append(pred) # 在预测中更新series(这里简单复制最后一个值) series = pd.concat([series, pd.Series([pred])]) return forecast # 预测未来7天 sales_series = time_series_data['销售额'].copy() forecast_days = 7 forecast = moving_average_forecast(sales_series, window_size=30, forecast_horizon=forecast_days) # 创建预测日期 forecast_dates = pd.date_range( start=time_series_data.index[-1] + pd.Timedelta(days=1), periods=forecast_days, freq='D' ) print(f"未来{forecast_days}天销售额预测:") for date, value in zip(forecast_dates, forecast): print(f" {date.date()}: {value:,.2f}") # 9. 时间序列可视化 print("\n9. 时间序列可视化:") fig, axes = plt.subplots(3, 2, figsize=(15, 12)) # 1. 原始时间序列 axes[0, 0].plot(time_series_data.index, time_series_data['销售额']) axes[0, 0].set_title('销售额时间序列') axes[0, 0].set_xlabel('日期') axes[0, 0].set_ylabel('销售额') axes[0, 0].grid(True, alpha=0.3) # 2. 移动平均 axes[0, 1].plot(time_series_data.index, time_series_data['销售额'], alpha=0.5, label='原始') axes[0, 1].plot(time_series_data.index, time_series_data['销售额_MA7'], label='7日移动平均') axes[0, 1].plot(time_series_data.index, time_series_data['销售额_MA30'], label='30日移动平均') axes[0, 1].set_title('移动平均分析') axes[0, 1].set_xlabel('日期') axes[0, 1].set_ylabel('销售额') axes[0, 1].legend() axes[0, 1].grid(True, alpha=0.3) # 3. 月度汇总 monthly_data['销售额_总和'].plot(kind='bar', ax=axes[1, 0]) axes[1, 0].set_title('月度销售额') axes[1, 0].set_xlabel('月份') axes[1, 0].set_ylabel('销售额') axes[1, 0].tick_params(axis='x', rotation=45) # 4. 星期分析 weekday_avg = time_series_data.groupby('星期')['销售额'].mean() weekday_avg.index = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] weekday_avg.plot(kind='bar', ax=axes[1, 1]) axes[1, 1].set_title('星期平均销售额') axes[1, 1].set_xlabel('星期') axes[1, 1].set_ylabel('平均销售额') # 5. 时间序列分解 if result is not None: axes[2, 0].plot(result.trend) axes[2, 0].set_title('趋势成分') axes[2, 0].set_xlabel('日期') axes[2, 0].set_ylabel('趋势') axes[2, 0].grid(True, alpha=0.3) axes[2, 1].plot(result.seasonal[:90]) # 只显示前90天的季节性 axes[2, 1].set_title('季节成分 (前90天)') axes[2, 1].set_xlabel('日期') axes[2, 1].set_ylabel('季节性') axes[2, 1].grid(True, alpha=0.3) plt.suptitle('时间序列分析可视化', fontsize=16, y=1.02) plt.tight_layout() plt.show() return { 'time_series_data': time_series_data, 'weekly_data': weekly_data, 'monthly_data': monthly_data, 'forecast': forecast, 'forecast_dates': forecast_dates, 'result': result }# 执行时间序列演示time_series_data = demonstrate_time_series()
七、数据合并与连接
def demonstrate_data_merging(): """演示数据合并与连接操作""" print("=" * 80) print("数据合并与连接") print("=" * 80) # 1. 创建示例数据集 print("1. 创建示例数据集") # 员工信息表 employees = pd.DataFrame({ '员工ID': ['E001', 'E002', 'E003', 'E004', 'E005'], '姓名': ['张三', '李四', '王五', '赵六', '钱七'], '部门': ['技术部', '市场部', '技术部', '人事部', '市场部'], '入职日期': pd.to_datetime(['2020-01-15', '2019-03-20', '2021-07-10', '2018-11-05', '2022-02-28']) }) # 工资表 salaries = pd.DataFrame({ '员工ID': ['E001', 'E002', 'E003', 'E004', 'E006'], # E005缺失,E006多余 '基本工资': [8000, 9000, 8500, 9500, 7800], '绩效奖金': [2000, 3000, 2500, 2800, 2200], '月份': '2024-01' }) # 部门信息表 departments = pd.DataFrame({ '部门': ['技术部', '市场部', '人事部', '财务部'], '部门经理': ['王经理', '李经理', '赵经理', '钱经理'], '预算': [1000000, 800000, 500000, 600000] }) # 项目分配表 projects = pd.DataFrame({ '员工ID': ['E001', 'E001', 'E002', 'E003', 'E004', 'E004'], '项目名称': ['项目A', '项目B', '项目A', '项目C', '项目B', '项目D'], '参与时长': [120, 80, 150, 90, 100, 60] # 小时 }) print(f"员工信息表:\n{employees}") print(f"\n工资表:\n{salaries}") print(f"\n部门信息表:\n{departments}") print(f"\n项目分配表:\n{projects}") # 2. 合并(Merge)操作 print("\n2. 合并(Merge)操作:") # 内连接(默认) inner_merge = pd.merge(employees, salaries, on='员工ID', how='inner') print(f"内连接 (只保留两边都有的员工):\n{inner_merge}") # 左连接 left_merge = pd.merge(employees, salaries, on='员工ID', how='left') print(f"\n左连接 (保留所有员工,工资缺失的为NaN):\n{left_merge}") # 右连接 right_merge = pd.merge(employees, salaries, on='员工ID', how='right') print(f"\n右连接 (保留所有工资记录,员工信息缺失的为NaN):\n{right_merge}") # 外连接 outer_merge = pd.merge(employees, salaries, on='员工ID', how='outer') print(f"\n外连接 (保留所有记录):\n{outer_merge}") # 3. 多对多合并 print("\n3. 多对多合并:") # 员工和项目是多对多关系 employee_projects = pd.merge(employees, projects, on='员工ID', how='left') print(f"员工项目分配:\n{employee_projects}") # 4. 多键合并 print("\n4. 多键合并:") # 创建包含年份的工资表 salaries_multi = pd.DataFrame({ '员工ID': ['E001', 'E001', 'E002', 'E002', 'E003'], '年份': [2023, 2024, 2023, 2024, 2024], '工资': [15000, 16000, 18000, 19000, 22000] }) # 员工年份信息 employee_years = pd.DataFrame({ '员工ID': ['E001', 'E001', 'E002', 'E002', 'E003'], '年份': [2023, 2024, 2023, 2024, 2024], '绩效评级': ['A', 'B', 'B', 'A', 'A'] }) multi_key_merge = pd.merge(salaries_multi, employee_years, on=['员工ID', '年份']) print(f"多键合并:\n{multi_key_merge}") # 5. 连接(Join)操作 print("\n5. 连接(Join)操作:") # 设置索引后连接 employees_idx = employees.set_index('员工ID') salaries_idx = salaries.set_index('员工ID') # 使用join方法 join_result = employees_idx.join(salaries_idx, how='left') print(f"索引连接:\n{join_result}") # 6. 拼接(Concat)操作 print("\n6. 拼接(Concat)操作:") # 垂直拼接(添加行) quarter1 = pd.DataFrame({ '产品': ['A', 'B', 'C'], 'Q1销售额': [1000, 1500, 1200] }) quarter2 = pd.DataFrame({ '产品': ['A', 'B', 'C'], 'Q2销售额': [1100, 1600, 1300] }) quarter3 = pd.DataFrame({ '产品': ['A', 'B', 'D'], # 注意产品D不在前两个季度 'Q3销售额': [1200, 1700, 900] }) # 简单拼接 vertical_concat = pd.concat([quarter1, quarter2, quarter3], ignore_index=True) print(f"垂直拼接:\n{vertical_concat}") # 水平拼接(添加列) horizontal_concat = pd.concat([quarter1, quarter2, quarter3], axis=1) print(f"\n水平拼接:\n{horizontal_concat}") # 更好的水平拼接方式 sales_data = quarter1.merge(quarter2, on='产品', how='outer') sales_data = sales_data.merge(quarter3, on='产品', how='outer') print(f"\n使用merge进行水平拼接:\n{sales_data}") # 7. 追加(Append)操作 print("\n7. 追加(Append)操作:") # 创建新员工 new_employee = pd.DataFrame({ '员工ID': ['E006'], '姓名': ['孙八'], '部门': ['财务部'], '入职日期': [pd.Timestamp('2023-05-15')] }) # 追加到员工表 employees_appended = employees.append(new_employee, ignore_index=True) print(f"追加新员工:\n{employees_appended}") # 8. 合并冲突处理 print("\n8. 合并冲突处理:") # 创建有重叠列的DataFrame df1 = pd.DataFrame({ 'ID': [1, 2, 3], '值': ['A', 'B', 'C'], '共同列': [10, 20, 30] }) df2 = pd.DataFrame({ 'ID': [2, 3, 4], '值': ['B2', 'C2', 'D2'], '共同列': [200, 300, 400] # 与df1的'共同列'冲突 }) # 合并时有冲突列 merge_conflict = pd.merge(df1, df2, on='ID', suffixes=('_df1', '_df2')) print(f"冲突列处理 (使用后缀):\n{merge_conflict}") # 9. 复杂合并:链式合并 print("\n9. 链式合并:") # 合并多个表 # 先合并员工和工资 merged_step1 = pd.merge(employees, salaries, on='员工ID', how='left') # 再合并部门信息 merged_step2 = pd.merge(merged_step1, departments, on='部门', how='left') # 最后合并项目信息(需要聚合) project_hours = projects.groupby('员工ID')['参与时长'].sum().reset_index() final_merged = pd.merge(merged_step2, project_hours, on='员工ID', how='left') # 计算总工资 final_merged['总工资'] = final_merged['基本工资'] + final_merged['绩效奖金'] final_merged['参与时长'] = final_merged['参与时长'].fillna(0) print(f"链式合并结果:\n{final_merged}") # 10. 性能优化技巧 print("\n10. 性能优化技巧:") # 创建大型数据集 np.random.seed(42) n_rows = 100000 large_df1 = pd.DataFrame({ 'ID': range(n_rows), '值1': np.random.randn(n_rows), '键': np.random.choice(['A', 'B', 'C', 'D', 'E'], n_rows) }) large_df2 = pd.DataFrame({ 'ID': range(n_rows // 2, n_rows + n_rows // 2), '值2': np.random.randn(n_rows), '键': np.random.choice(['A', 'B', 'C', 'D', 'E'], n_rows) }) # 测试不同合并方法的性能 import time methods = ['merge', 'join', 'concat'] times = {} for method in methods: start = time.time() if method == 'merge': result = pd.merge(large_df1, large_df2, on='键', how='inner') elif method == 'join': df1_idx = large_df1.set_index('键') df2_idx = large_df2.set_index('键') result = df1_idx.join(df2_idx, how='inner', lsuffix='_df1', rsuffix='_df2') else: # concat result = pd.concat([large_df1, large_df2], axis=1) elapsed = time.time() - start times[method] = elapsed print(f"{method}: {elapsed:.3f}秒, 结果形状: {result.shape}") # 可视化合并性能 fig, ax = plt.subplots(figsize=(10, 6)) bars = ax.bar(times.keys(), times.values()) ax.set_title('不同合并方法性能比较 (10万行数据)') ax.set_ylabel('时间 (秒)') ax.set_xlabel('合并方法') # 添加数值标签 for bar in bars: height = bar.get_height() ax.text(bar.get_x() + bar.get_width()/2., height + 0.01, f'{height:.3f}', ha='center', va='bottom') plt.tight_layout() plt.show() return { 'employees': employees, 'salaries': salaries, 'departments': departments, 'projects': projects, 'inner_merge': inner_merge, 'final_merged': final_merged, 'merge_performance': times }# 执行数据合并演示merge_data = demonstrate_data_merging()
八、实际案例分析:电商数据分析
def ecommerce_case_study(): """电商数据分析实战案例""" print("=" * 80) print("实战案例:电商数据分析") print("=" * 80) # 1. 数据准备 print("1. 数据准备") np.random.seed(42) n_customers = 1000 n_products = 50 n_transactions = 5000 # 生成客户数据 customers = pd.DataFrame({ '客户ID': [f'CUST{i:04d}' for i in range(1, n_customers + 1)], '注册日期': pd.to_datetime(np.random.choice( pd.date_range('2022-01-01', '2024-01-01', freq='D'), n_customers )), '城市': np.random.choice(['北京', '上海', '广州', '深圳', '杭州', '成都', '武汉', '南京'], n_customers, p=[0.15, 0.15, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]), '年龄': np.random.randint(18, 60, n_customers), '性别': np.random.choice(['男', '女'], n_customers, p=[0.55, 0.45]), '会员等级': np.random.choice(['普通', '白银', '黄金', '铂金'], n_customers, p=[0.4, 0.3, 0.2, 0.1]) }) # 生成产品数据 categories = ['电子产品', '服装', '家居', '美妆', '食品', '图书'] products = pd.DataFrame({ '产品ID': [f'PROD{i:03d}' for i in range(1, n_products + 1)], '产品名称': [f'产品{i}' for i in range(1, n_products + 1)], '类别': np.random.choice(categories, n_products), '子类别': np.random.choice(['手机', '电脑', '上衣', '裤子', '家具', '餐具', '护肤品', '化妆品', '零食', '饮料', '小说', '教材'], n_products), '成本价': np.round(np.random.uniform(50, 500, n_products), 2), '售价': np.round(np.random.uniform(100, 1000, n_products), 2), '库存': np.random.randint(0, 100, n_products) }) # 计算利润率 products['利润率'] = (products['售价'] - products['成本价']) / products['售价'] # 生成交易数据 transactions = pd.DataFrame({ '订单ID': [f'ORDER{i:05d}' for i in range(1, n_transactions + 1)], '客户ID': np.random.choice(customers['客户ID'], n_transactions), '产品ID': np.random.choice(products['产品ID'], n_transactions), '购买日期': pd.to_datetime(np.random.choice( pd.date_range('2023-01-01', '2024-01-01', freq='D'), n_transactions )), '数量': np.random.randint(1, 5, n_transactions), '折扣': np.random.choice([1.0, 0.95, 0.9, 0.85], n_transactions, p=[0.5, 0.3, 0.15, 0.05]) }) print(f"客户数据: {customers.shape}") print(f"产品数据: {products.shape}") print(f"交易数据: {transactions.shape}") # 2. 数据合并 print("\n2. 数据合并") # 合并交易和产品数据 merged_data = pd.merge(transactions, products, on='产品ID', how='left') # 合并客户数据 merged_data = pd.merge(merged_data, customers, on='客户ID', how='left') # 计算交易金额 merged_data['交易金额'] = merged_data['数量'] * merged_data['售价'] * merged_data['折扣'] merged_data['成本'] = merged_data['数量'] * merged_data['成本价'] merged_data['利润'] = merged_data['交易金额'] - merged_data['成本'] print(f"合并后数据形状: {merged_data.shape}") # 3. 数据分析 print("\n3. 数据分析") # 3.1 总体销售情况 total_sales = merged_data['交易金额'].sum() total_profit = merged_data['利润'].sum() total_transactions = merged_data['订单ID'].nunique() total_customers = merged_data['客户ID'].nunique() print("总体销售情况:") print(f" 总销售额: ¥{total_sales:,.2f}") print(f" 总利润: ¥{total_profit:,.2f}") print(f" 总订单数: {total_transactions}") print(f" 总客户数: {total_customers}") print(f" 客单价: ¥{total_sales/total_transactions:,.2f}") print(f" 利润率: {total_profit/total_sales:.2%}") # 3.2 月度销售趋势 monthly_sales = merged_data.groupby(pd.Grouper(key='购买日期', freq='M')).agg({ '订单ID': 'nunique', '交易金额': 'sum', '利润': 'sum', '客户ID': 'nunique' }).rename(columns={ '订单ID': '订单数', '交易金额': '销售额', '客户ID': '客户数' }) monthly_sales['客单价'] = monthly_sales['销售额'] / monthly_sales['订单数'] monthly_sales['利润率'] = monthly_sales['利润'] / monthly_sales['销售额'] print(f"\n月度销售趋势:\n{monthly_sales.round(2)}") # 3.3 产品分析 product_analysis = merged_data.groupby(['产品ID', '产品名称', '类别']).agg({ '订单ID': 'count', '交易金额': 'sum', '利润': 'sum', '数量': 'sum' }).rename(columns={ '订单ID': '销量', '交易金额': '销售额', '数量': '总销量' }) product_analysis['利润率'] = product_analysis['利润'] / product_analysis['销售额'] product_analysis = product_analysis.sort_values('销售额', ascending=False) print(f"\n热销产品Top 10:\n{product_analysis.head(10).round(2)}") # 3.4 客户分析 customer_analysis = merged_data.groupby('客户ID').agg({ '订单ID': 'nunique', '交易金额': 'sum', '利润': 'sum', '购买日期': ['min', 'max', 'count'] }) # 扁平化列名 customer_analysis.columns = ['订单数', '总消费', '总利润', '首次购买', '最后购买', '购买次数'] # 计算RFM指标 reference_date = merged_data['购买日期'].max() + pd.Timedelta(days=1) customer_analysis['最近消费天数'] = (reference_date - customer_analysis['最后购买']).dt.days customer_analysis['购买频率'] = customer_analysis['购买次数'] / customer_analysis['订单数'] customer_analysis['平均订单价值'] = customer_analysis['总消费'] / customer_analysis['订单数'] print(f"\n高价值客户Top 10:\n{customer_analysis.sort_values('总消费', ascending=False).head(10).round(2)}") # 3.5 地理分析 city_analysis = merged_data.groupby('城市').agg({ '客户ID': 'nunique', '订单ID': 'nunique', '交易金额': 'sum', '利润': 'sum' }).rename(columns={ '客户ID': '客户数', '订单ID': '订单数', '交易金额': '销售额' }) city_analysis['客单价'] = city_analysis['销售额'] / city_analysis['订单数'] city_analysis = city_analysis.sort_values('销售额', ascending=False) print(f"\n城市销售分析:\n{city_analysis.round(2)}") # 3.6 用户分群 print("\n4. 用户分群 (RFM分析)") # RFM分箱 customer_analysis['R_Score'] = pd.qcut(customer_analysis['最近消费天数'], q=4, labels=[4, 3, 2, 1]) customer_analysis['F_Score'] = pd.qcut(customer_analysis['购买频率'], q=4, labels=[1, 2, 3, 4]) customer_analysis['M_Score'] = pd.qcut(customer_analysis['平均订单价值'], q=4, labels=[1, 2, 3, 4]) # 计算RFM总分 customer_analysis['RFM_Score'] = ( customer_analysis['R_Score'].astype(str) + customer_analysis['F_Score'].astype(str) + customer_analysis['M_Score'].astype(str) ) # RFM分群 def rfm_segment(row): if row['R_Score'] >= 3 and row['F_Score'] >= 3 and row['M_Score'] >= 3: return '高价值客户' elif row['R_Score'] >= 2 and row['F_Score'] >= 2: return '潜力客户' elif row['R_Score'] >= 2: return '新客户' elif row['F_Score'] >= 2: return '忠诚客户' else: return '流失客户' customer_analysis['客户分群'] = customer_analysis.apply(rfm_segment, axis=1) segment_stats = customer_analysis.groupby('客户分群').agg({ '客户ID': 'count', '总消费': 'sum', '平均订单价值': 'mean' }).rename(columns={'客户ID': '客户数'}) print(f"客户分群结果:\n{segment_stats.round(2)}") # 4. 可视化分析 print("\n5. 可视化分析") fig, axes = plt.subplots(2, 3, figsize=(18, 12)) # 1. 月度销售趋势 axes[0, 0].plot(monthly_sales.index, monthly_sales['销售额'], marker='o') axes[0, 0].set_title('月度销售额趋势') axes[0, 0].set_xlabel('月份') axes[0, 0].set_ylabel('销售额') axes[0, 0].grid(True, alpha=0.3) axes[0, 0].tick_params(axis='x', rotation=45) # 2. 产品类别销售额 category_sales = merged_data.groupby('类别')['交易金额'].sum().sort_values() axes[0, 1].barh(category_sales.index, category_sales.values) axes[0, 1].set_title('各品类销售额') axes[0, 1].set_xlabel('销售额') # 3. 城市销售分布 city_sales = city_analysis['销售额'].sort_values(ascending=False) axes[0, 2].bar(city_sales.index, city_sales.values) axes[0, 2].set_title('城市销售额分布') axes[0, 2].set_xlabel('城市') axes[0, 2].set_ylabel('销售额') axes[0, 2].tick_params(axis='x', rotation=45) # 4. 客户分群分布 segment_counts = segment_stats['客户数'] axes[1, 0].pie(segment_counts.values, labels=segment_counts.index, autopct='%1.1f%%') axes[1, 0].set_title('客户分群占比') # 5. 热销产品Top 10 top_products = product_analysis.head(10) axes[1, 1].barh(top_products.index.get_level_values(1), top_products['销售额']) axes[1, 1].set_title('热销产品Top 10') axes[1, 1].set_xlabel('销售额') # 6. RFM散点图 scatter_data = customer_analysis.sample(100, random_state=42) # 抽样显示 colors = {'高价值客户': 'green', '潜力客户': 'blue', '新客户': 'orange', '忠诚客户': 'purple', '流失客户': 'red'} for segment, color in colors.items(): segment_data = scatter_data[scatter_data['客户分群'] == segment] axes[1, 2].scatter(segment_data['最近消费天数'], segment_data['平均订单价值'], c=color, label=segment, alpha=0.6) axes[1, 2].set_title('RFM分析散点图') axes[1, 2].set_xlabel('最近消费天数 (Recency)') axes[1, 2].set_ylabel('平均订单价值 (Monetary)') axes[1, 2].legend() axes[1, 2].grid(True, alpha=0.3) plt.suptitle('电商数据分析仪表板', fontsize=16, y=1.02) plt.tight_layout() plt.show() # 5. 生成分析报告 print("\n6. 生成分析报告摘要") report = f""" ============================================ 电商数据分析报告 ============================================ 1. 总体表现: - 总销售额: ¥{total_sales:,.2f} - 总利润: ¥{total_profit:,.2f} - 总订单数: {total_transactions} - 总客户数: {total_customers} - 客单价: ¥{total_sales/total_transactions:,.2f} - 整体利润率: {total_profit/total_sales:.2%} 2. 月度趋势: - 最高月销售额: ¥{monthly_sales['销售额'].max():,.2f} ({monthly_sales['销售额'].idxmax().strftime('%Y-%m')}) - 最低月销售额: ¥{monthly_sales['销售额'].min():,.2f} ({monthly_sales['销售额'].idxmin().strftime('%Y-%m')}) - 月均销售额: ¥{monthly_sales['销售额'].mean():,.2f} 3. 产品分析: - 最畅销产品: {product_analysis.index[0][1]} (¥{product_analysis.iloc[0]['销售额']:,.2f}) - 最高利润产品: {product_analysis.sort_values('利润', ascending=False).index[0][1]} - 产品类别数: {len(product_analysis.index.get_level_values('类别').unique())} 4. 客户分析: - 高价值客户占比: {segment_stats.loc['高价值客户', '客户数']/segment_stats['客户数'].sum():.1%} - 平均客户生命周期价值: ¥{customer_analysis['总消费'].mean():,.2f} - 客户回购率: {(customer_analysis['订单数'] > 1).sum()/len(customer_analysis):.1%} 5. 地理分析: - 销售额最高城市: {city_analysis.index[0]} (¥{city_analysis.iloc[0]['销售额']:,.2f}) - 城市覆盖率: {len(city_analysis)} 个城市 6. 业务建议: - 重点关注高价值客户维护 - 加大热销产品的库存和推广 - 在销售额高的城市增加营销投入 - 针对流失客户设计召回活动 """ print(report) return { 'customers': customers, 'products': products, 'transactions': transactions, 'merged_data': merged_data, 'monthly_sales': monthly_sales, 'product_analysis': product_analysis, 'customer_analysis': customer_analysis, 'city_analysis': city_analysis, 'segment_stats': segment_stats, 'report': report }# 执行电商案例ecommerce_analysis = ecommerce_case_study()
Pandas是Python数据科学生态系统的核心。掌握Pandas不仅是学习一个库,而是掌握了一套数据处理和分析的方法论。
明日将进入特征工程的世界,这是机器学习项目中最重要的环节之一。准备好将原始数据转化为机器学习模型的特征!