引言

pandas是Python中最流行的数据分析库之一,它提供了高性能、易于使用的数据结构和数据分析工具。无论你是数据分析师、数据科学家还是研究人员,掌握pandas都将大大提高你的数据处理效率。本文将从基础概念开始,通过丰富的实战案例,手把手教你掌握pandas的数据处理、分析与可视化技能。

pandas的核心优势在于其强大的数据操作能力,它能够处理各种类型的数据,包括数值型、时间序列、表格数据等。通过pandas,你可以轻松完成数据清洗、转换、分析、可视化等一系列数据科学任务。在当今数据驱动的时代,掌握pandas技能已经成为数据从业者的必备能力。

pandas基础入门

安装与导入

在开始使用pandas之前,首先需要安装它。最简单的方式是通过pip进行安装:

pip install pandas 

或者,如果你使用的是Anaconda发行版,可以通过conda安装:

conda install pandas 

安装完成后,在Python脚本或Jupyter Notebook中导入pandas:

import pandas as pd 

通常,我们使用pd作为pandas的别名,这是pandas社区广泛采用的惯例。

pandas核心数据结构

pandas有两个核心数据结构:Series和DataFrame。

Series

Series是一维标记数组,能够保存任何数据类型(整数、字符串、浮点数、Python对象等)。Series类似于带标签的NumPy数组。

创建Series的基本语法:

# 创建一个简单的Series import numpy as np s = pd.Series([1, 3, 5, np.nan, 6, 8]) print(s) 

输出:

0 1.0 1 3.0 2 5.0 3 NaN 4 6.0 5 8.0 dtype: float64 

Series由两部分组成:values和index。values是Series中的数据,index是数据的标签。如果不指定index,pandas会自动创建一个从0开始的整数索引。

# 创建带有自定义索引的Series s = pd.Series([1, 3, 5, 7, 9], index=['a', 'b', 'c', 'd', 'e']) print(s) 

输出:

a 1 b 3 c 5 d 7 e 9 dtype: int64 

可以通过索引访问Series中的元素:

# 通过索引访问元素 print(s['a']) # 输出: 1 print(s[0]) # 输出: 1 

DataFrame

DataFrame是二维的、大小可变的、 potentially heterogeneous的表格型数据结构,带有标记的轴(行和列)。可以把它看作是一个Series对象的字典。

创建DataFrame的基本语法:

# 创建一个简单的DataFrame data = {'Name': ['John', 'Anna', 'Peter', 'Linda'], 'Age': [28, 34, 29, 42], 'City': ['New York', 'Paris', 'Berlin', 'London']} df = pd.DataFrame(data) print(df) 

输出:

 Name Age City 0 John 28 New York 1 Anna 34 Paris 2 Peter 29 Berlin 3 Linda 42 London 

DataFrame由三部分组成:data、index和columns。data是DataFrame中的数据,index是行标签,columns是列标签。

可以通过列名访问DataFrame中的列:

# 访问列 print(df['Name']) 

输出:

0 John 1 Anna 2 Peter 3 Linda Name: Name, dtype: object 

也可以使用loc和iloc访问DataFrame中的元素:

# 使用loc(基于标签)访问元素 print(df.loc[0, 'Name']) # 输出: John # 使用iloc(基于位置)访问元素 print(df.iloc[0, 0]) # 输出: John 

数据导入与导出

在实际数据分析中,数据通常存储在外部文件中,如CSV、Excel、SQL数据库等。pandas提供了丰富的函数来读取和写入各种格式的数据。

读取CSV文件

CSV是最常见的数据存储格式之一。使用pandas读取CSV文件非常简单:

# 读取CSV文件 df = pd.read_csv('data.csv') # 如果文件没有标题行,可以使用header参数 df = pd.read_csv('data.csv', header=None) # 如果文件有标题行,但想指定自己的列名 df = pd.read_csv('data.csv', names=['Column1', 'Column2', 'Column3']) # 只读取前n行 df = pd.read_csv('data.csv', nrows=100) # 跳过前n行 df = pd.read_csv('data.csv', skiprows=2) 

读取Excel文件

Excel文件也是常见的数据存储格式。pandas需要额外的库来读取Excel文件:

pip install openpyxl xlrd 

然后可以使用以下代码读取Excel文件:

# 读取Excel文件 df = pd.read_excel('data.xlsx', sheet_name='Sheet1') # 如果Excel文件有多个工作表,可以读取所有工作表 all_sheets = pd.read_excel('data.xlsx', sheet_name=None) for sheet_name, df in all_sheets.items(): print(f"Sheet name: {sheet_name}") print(df.head()) 

读取SQL数据库

如果数据存储在SQL数据库中,可以使用pandas的read_sql函数:

import sqlite3 # 创建数据库连接 conn = sqlite3.connect('database.db') # 读取SQL查询结果到DataFrame df = pd.read_sql('SELECT * FROM table_name', conn) # 关闭连接 conn.close() 

写入数据

pandas也提供了将DataFrame写入各种格式的函数:

# 写入CSV文件 df.to_csv('output.csv', index=False) # index=False表示不写入索引 # 写入Excel文件 df.to_excel('output.xlsx', sheet_name='Sheet1', index=False) # 写入SQL数据库 conn = sqlite3.connect('database.db') df.to_sql('table_name', conn, if_exists='replace', index=False) conn.close() 

数据清洗与预处理

在实际数据分析中,原始数据往往包含各种问题,如缺失值、重复值、异常值等。数据清洗是数据分析过程中非常重要的一步。

处理缺失值

缺失值是数据集中常见的问题。pandas使用NaN(Not a Number)表示缺失值。

# 检测缺失值 print(df.isnull()) # 返回一个布尔DataFrame,表示哪些值是缺失的 print(df.isnull().sum()) # 计算每列的缺失值数量 # 删除缺失值 df.dropna() # 删除包含缺失值的行 df.dropna(axis=1) # 删除包含缺失值的列 df.dropna(how='all') # 只删除所有值都缺失的行 # 填充缺失值 df.fillna(0) # 用0填充所有缺失值 df.fillna({'Column1': 0, 'Column2': 'missing'}) # 为不同列指定不同的填充值 df.fillna(method='ffill') # 前向填充,用前一个非缺失值填充 df.fillna(method='bfill') # 后向填充,用后一个非缺失值填充 # 使用均值、中位数等填充 df['Column1'].fillna(df['Column1'].mean(), inplace=True) 

处理重复值

重复值会影响数据分析的结果,需要识别和处理。

# 检测重复值 print(df.duplicated()) # 返回一个布尔Series,表示哪些行是重复的 print(df.duplicated().sum()) # 计算重复行的数量 # 删除重复值 df.drop_duplicates() # 删除所有重复的行 df.drop_duplicates(subset=['Column1', 'Column2']) # 基于特定列删除重复行 df.drop_duplicates(keep='last') # 保留最后一个重复行,而不是第一个 

处理异常值

异常值是数据集中明显偏离其他观测值的值。处理异常值的方法有很多,取决于具体的数据和分析目标。

# 使用Z-score检测异常值 from scipy import stats import numpy as np z_scores = np.abs(stats.zscore(df['Column1'])) threshold = 3 outliers = np.where(z_scores > threshold) print(outliers) # 使用IQR(四分位距)检测异常值 Q1 = df['Column1'].quantile(0.25) Q3 = df['Column1'].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR outliers = df[(df['Column1'] < lower_bound) | (df['Column1'] > upper_bound)] print(outliers) # 处理异常值 # 删除异常值 df = df[(df['Column1'] >= lower_bound) & (df['Column1'] <= upper_bound)] # 替换异常值 df.loc[df['Column1'] > upper_bound, 'Column1'] = upper_bound df.loc[df['Column1'] < lower_bound, 'Column1'] = lower_bound 

数据类型转换

在数据分析中,经常需要将数据转换为适当的类型。

# 查看数据类型 print(df.dtypes) # 转换数据类型 df['Column1'] = df['Column1'].astype('int') # 转换为整数类型 df['Column2'] = df['Column2'].astype('float') # 转换为浮点数类型 df['Column3'] = df['Column3'].astype('category') # 转换为分类类型 df['Column4'] = pd.to_datetime(df['Column4']) # 转换为日期时间类型 

数据选择与过滤

在数据分析中,经常需要选择数据的子集或基于条件过滤数据。pandas提供了多种方法来实现这些操作。

选择列

# 选择单列 column1 = df['Column1'] # 返回一个Series column1 = df.Column1 # 同上,但这种方法不适用于列名包含空格或特殊字符的情况 # 选择多列 subset = df[['Column1', 'Column2', 'Column3']] # 返回一个DataFrame 

选择行

# 使用loc选择行(基于标签) row = df.loc[0] # 选择索引为0的行 rows = df.loc[0:5] # 选择索引从0到5的行(包括5) # 使用iloc选择行(基于位置) row = df.iloc[0] # 选择第一行 rows = df.iloc[0:5] # 选择前5行(不包括5) 

同时选择行和列

# 使用loc subset = df.loc[0:5, ['Column1', 'Column2']] # 选择索引从0到5的行,以及Column1和Column2列 # 使用iloc subset = df.iloc[0:5, 0:2] # 选择前5行和前2列 

基于条件过滤

# 单条件过滤 filtered = df[df['Column1'] > 5] # 选择Column1大于5的所有行 # 多条件过滤(AND) filtered = df[(df['Column1'] > 5) & (df['Column2'] == 'A')] # 选择Column1大于5且Column2等于'A'的所有行 # 多条件过滤(OR) filtered = df[(df['Column1'] > 5) | (df['Column2'] == 'A')] # 选择Column1大于5或Column2等于'A'的所有行 # 使用isin方法 filtered = df[df['Column2'].isin(['A', 'B', 'C'])] # 选择Column2等于'A'、'B'或'C'的所有行 # 使用str方法进行字符串匹配 filtered = df[df['Name'].str.startswith('J')] # 选择Name以'J'开头的所有行 filtered = df[df['Name'].str.contains('oh')] # 选择Name包含'oh'的所有行 

使用query方法

pandas的query方法提供了一种更直观的方式来过滤数据:

# 使用query方法 filtered = df.query('Column1 > 5 and Column2 == "A"') # 可以在查询中使用外部变量 threshold = 5 filtered = df.query('Column1 > @threshold') 

数据转换与处理

在数据分析过程中,经常需要对数据进行转换和处理,以便更好地理解数据或为后续分析做准备。

应用函数

pandas提供了多种方法来应用函数到数据上。

# 应用函数到Series df['Column1'] = df['Column1'].apply(lambda x: x * 2) # 将Column1中的每个值乘以2 df['Column1'] = df['Column1'].apply(np.sqrt) # 计算Column1中每个值的平方根 # 应用函数到DataFrame的列 df[['Column1', 'Column2']] = df[['Column1', 'Column2']].apply(lambda x: x * 2) # 应用函数到DataFrame的行 df['NewColumn'] = df.apply(lambda row: row['Column1'] + row['Column2'], axis=1) # 使用applymap将函数应用到DataFrame的每个元素 df = df.applymap(lambda x: str(x) if isinstance(x, (int, float)) else x) 

排序数据

# 按列排序 df.sort_values('Column1') # 按Column1升序排序 df.sort_values('Column1', ascending=False) # 按Column1降序排序 # 按多列排序 df.sort_values(['Column1', 'Column2']) # 先按Column1排序,然后按Column2排序 df.sort_values(['Column1', 'Column2'], ascending=[True, False]) # Column1升序,Column2降序 # 按索引排序 df.sort_index() # 按索引升序排序 df.sort_index(ascending=False) # 按索引降序排序 

分组与聚合

分组与聚合是数据分析中常用的操作,pandas提供了groupby方法来实现这些功能。

# 按列分组并计算聚合统计量 grouped = df.groupby('Category') print(grouped.mean()) # 计算每组的平均值 print(grouped.sum()) # 计算每组的总和 print(grouped.count()) # 计算每组的数量 print(grouped.min()) # 计算每组的最小值 print(grouped.max()) # 计算每组的最大值 # 使用agg方法应用多个聚合函数 print(grouped.agg(['mean', 'sum', 'count'])) # 对不同列应用不同的聚合函数 print(grouped.agg({'Column1': 'mean', 'Column2': 'sum', 'Column3': 'count'})) # 使用自定义聚合函数 print(grouped.agg({'Column1': lambda x: x.max() - x.min()})) # 多级分组 grouped = df.groupby(['Category1', 'Category2']) print(grouped.mean()) 

透视表

透视表是一种强大的数据汇总工具,可以按照不同的维度对数据进行汇总。

# 创建透视表 pivot = pd.pivot_table(df, values='Value', index='Category1', columns='Category2', aggfunc='mean') # 使用多个聚合函数 pivot = pd.pivot_table(df, values='Value', index='Category1', columns='Category2', aggfunc=['mean', 'sum']) # 添加总计 pivot = pd.pivot_table(df, values='Value', index='Category1', columns='Category2', aggfunc='sum', margins=True) # 使用多个值列 pivot = pd.pivot_table(df, values=['Value1', 'Value2'], index='Category1', columns='Category2', aggfunc='sum') 

交叉表

交叉表用于计算两个或多个因素的频率分布。

# 创建交叉表 cross_tab = pd.crosstab(df['Category1'], df['Category2']) # 添加行和列的总计 cross_tab = pd.crosstab(df['Category1'], df['Category2'], margins=True) # 计算百分比 cross_tab = pd.crosstab(df['Category1'], df['Category2'], normalize='index') # 行百分比 cross_tab = pd.crosstab(df['Category1'], df['Category2'], normalize='columns') # 列百分比 cross_tab = pd.crosstab(df['Category1'], df['Category2'], normalize='all') # 总百分比 

数据合并与重塑

在数据分析中,经常需要将多个数据集合并在一起,或者重塑数据的结构以便于分析。

合并数据

pandas提供了多种方法来合并数据,包括merge、join和concat。

merge

merge类似于SQL中的JOIN操作,基于一个或多个键将两个DataFrame合并。

# 创建两个DataFrame df1 = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'value': [1, 2, 3, 4]}) df2 = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'value': [5, 6, 7, 8]}) # 内连接(只保留两个DataFrame中都存在的键) merged = pd.merge(df1, df2, on='key') # 左连接(保留左DataFrame的所有键) merged = pd.merge(df1, df2, on='key', how='left') # 右连接(保留右DataFrame的所有键) merged = pd.merge(df1, df2, on='key', how='right') # 外连接(保留两个DataFrame的所有键) merged = pd.merge(df1, df2, on='key', how='outer') # 基于多个键合并 df1 = pd.DataFrame({'key1': ['A', 'B', 'C', 'D'], 'key2': ['K', 'L', 'M', 'N'], 'value': [1, 2, 3, 4]}) df2 = pd.DataFrame({'key1': ['B', 'D', 'E', 'F'], 'key2': ['L', 'N', 'O', 'P'], 'value': [5, 6, 7, 8]}) merged = pd.merge(df1, df2, on=['key1', 'key2']) # 处理列名冲突 merged = pd.merge(df1, df2, on='key', suffixes=('_left', '_right')) 

join

join是基于索引的合并方法,更简洁但功能不如merge强大。

# 创建两个DataFrame df1 = pd.DataFrame({'value': [1, 2, 3, 4]}, index=['A', 'B', 'C', 'D']) df2 = pd.DataFrame({'value': [5, 6, 7, 8]}, index=['B', 'D', 'E', 'F']) # 内连接 joined = df1.join(df2, how='inner') # 左连接 joined = df1.join(df2, how='left') # 右连接 joined = df1.join(df2, how='right') # 外连接 joined = df1.join(df2, how='outer') 

concat

concat用于沿着一个轴将多个对象堆叠在一起。

# 创建两个DataFrame df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'], 'B': ['B0', 'B1', 'B2', 'B3']}) df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'], 'B': ['B4', 'B5', 'B6', 'B7']}) # 垂直堆叠(行方向) concatenated = pd.concat([df1, df2]) # 水平堆叠(列方向) concatenated = pd.concat([df1, df2], axis=1) # 忽略索引 concatenated = pd.concat([df1, df2], ignore_index=True) # 添加键以标识原始DataFrame concatenated = pd.concat([df1, df2], keys=['df1', 'df2']) 

重塑数据

重塑数据是指改变数据的结构,通常是为了便于分析或可视化。

melt

melt用于将宽格式数据转换为长格式数据。

# 创建一个宽格式的DataFrame df = pd.DataFrame({ 'Name': ['John', 'Anna', 'Peter', 'Linda'], 'Math': [90, 85, 88, 92], 'Physics': [85, 80, 78, 90], 'Chemistry': [88, 82, 85, 87] }) # 使用melt转换为长格式 melted = pd.melt(df, id_vars=['Name'], var_name='Subject', value_name='Score') 

pivot

pivot是melt的逆操作,用于将长格式数据转换为宽格式数据。

# 使用pivot将长格式数据转换为宽格式数据 pivoted = melted.pivot(index='Name', columns='Subject', values='Score') # 重置索引,将Name转换为列 pivoted = pivoted.reset_index() 

stack和unstack

stack和unstack用于在层级索引和列之间转换数据。

# 创建一个带有多级列的DataFrame df = pd.DataFrame({ ('Math', 'Score'): [90, 85, 88, 92], ('Math', 'Grade'): ['A', 'B', 'B', 'A'], ('Physics', 'Score'): [85, 80, 78, 90], ('Physics', 'Grade'): ['B', 'C', 'C', 'B'] }, index=['John', 'Anna', 'Peter', 'Linda']) # 使用stack将列转换为行 stacked = df.stack() # 使用unstack将行转换为列 unstacked = stacked.unstack() 

时间序列数据处理

时间序列数据是数据分析中常见的数据类型,pandas提供了强大的时间序列处理功能。

创建时间序列

# 创建一个时间序列 dates = pd.date_range('20230101', periods=6) ts = pd.Series(np.random.randn(6), index=dates) print(ts) 

时间序列索引

# 选择特定日期的数据 print(ts['2023-01-01']) # 选择特定年份的数据 print(ts['2023']) # 选择特定月份的数据 print(ts['2023-01']) # 使用切片选择日期范围 print(ts['2023-01-01':'2023-01-03']) 

时间序列重采样

重采样是指将时间序列从一个频率转换到另一个频率。

# 创建一个更高频率的时间序列 dates = pd.date_range('20230101', periods=100, freq='D') ts = pd.Series(np.random.randn(100), index=dates) # 将日数据重采样为月数据,并计算每月的平均值 monthly = ts.resample('M').mean() # 将日数据重采样为周数据,并计算每周的总和 weekly = ts.resample('W').sum() # 其他重采样频率 # 'H':小时 # 'T'或'min':分钟 # 'S':秒 # 'L'或'ms':毫秒 # 'U':微秒 # 'N':纳秒 

时间序列窗口函数

窗口函数用于计算时间序列的滚动统计量。

# 计算滚动平均值 rolling_mean = ts.rolling(window=7).mean() # 7天滚动平均 # 计算滚动标准差 rolling_std = ts.rolling(window=7).std() # 计算滚动最大值 rolling_max = ts.rolling(window=7).max() # 计算滚动最小值 rolling_min = ts.rolling(window=7).min() # 计算扩展窗口统计量 expanding_mean = ts.expanding().mean() # 扩展窗口平均 # 计算指数加权移动平均 ewm_mean = ts.ewm(span=7).mean() # 7天指数加权移动平均 

时间序列偏移和滞后

# 创建一个时间序列 dates = pd.date_range('20230101', periods=5) ts = pd.Series([1, 2, 3, 4, 5], index=dates) # 偏移时间序列 shifted_forward = ts.shift(1) # 向前偏移1天 shifted_backward = ts.shift(-1) # 向后偏移1天 # 计算时间序列的变化 change = ts - ts.shift(1) # 计算每日变化 # 计算时间序列的百分比变化 pct_change = ts.pct_change() # 计算每日百分比变化 

数据可视化基础

数据可视化是数据分析的重要组成部分,可以帮助我们更好地理解数据。pandas与matplotlib和seaborn等可视化库紧密集成,提供了便捷的绘图功能。

使用pandas内置绘图功能

pandas提供了简单的绘图方法,基于matplotlib。

# 导入matplotlib import matplotlib.pyplot as plt # 创建一个DataFrame df = pd.DataFrame({ 'A': np.random.randn(100).cumsum(), 'B': np.random.randn(100).cumsum(), 'C': np.random.randn(100).cumsum() }, index=pd.date_range('20230101', periods=100)) # 绘制线图 df.plot() plt.show() # 绘制柱状图 df.plot(kind='bar') plt.show() # 绘制直方图 df['A'].plot(kind='hist', bins=20) plt.show() # 绘制箱线图 df.plot(kind='box') plt.show() # 绘制散点图 df.plot(kind='scatter', x='A', y='B') plt.show() # 绘制面积图 df.plot(kind='area', stacked=False) plt.show() 

使用seaborn进行高级可视化

seaborn是一个基于matplotlib的统计数据可视化库,提供了更高级的绘图功能和更美观的默认样式。

# 导入seaborn import seaborn as sns # 设置seaborn样式 sns.set(style="whitegrid") # 创建一个DataFrame df = pd.DataFrame({ 'x': np.random.randn(100), 'y': np.random.randn(100), 'category': np.random.choice(['A', 'B', 'C'], 100) }) # 绘制散点图 sns.scatterplot(x='x', y='y', hue='category', data=df) plt.show() # 绘制线图 sns.lineplot(data=df[['x', 'y']]) plt.show() # 绘制分布图 sns.distplot(df['x'], bins=20) plt.show() # 绘制箱线图 sns.boxplot(x='category', y='x', data=df) plt.show() # 绘制小提琴图 sns.violinplot(x='category', y='x', data=df) plt.show() # 绘制成对关系图 sns.pairplot(df) plt.show() # 绘制热力图 corr = df.corr() sns.heatmap(corr, annot=True, cmap='coolwarm') plt.show() 

使用plotly进行交互式可视化

plotly是一个交互式可视化库,可以创建交互式的图表。

pip install plotly 
# 导入plotly import plotly.express as px # 创建一个DataFrame df = pd.DataFrame({ 'x': np.random.randn(100), 'y': np.random.randn(100), 'category': np.random.choice(['A', 'B', 'C'], 100) }) # 绘制散点图 fig = px.scatter(df, x='x', y='y', color='category') fig.show() # 绘制线图 df['date'] = pd.date_range('20230101', periods=100) df['value'] = np.random.randn(100).cumsum() fig = px.line(df, x='date', y='value') fig.show() # 绘制直方图 fig = px.histogram(df, x='x', nbins=20) fig.show() # 绘制箱线图 fig = px.box(df, x='category', y='x') fig.show() # 绘制热力图 corr = df.corr() fig = px.imshow(corr, text_auto=True, color_continuous_scale='RdBu') fig.show() 

高级数据分析技巧

在掌握了pandas的基础知识后,我们可以学习一些高级技巧,以提高数据分析的效率和灵活性。

性能优化

在处理大型数据集时,性能优化非常重要。

# 使用适当的数据类型 df['column'] = df['column'].astype('category') # 对于低基数的字符串列,使用category类型可以节省内存 df['column'] = df['column'].astype('float32') # 对于浮点数,如果精度要求不高,可以使用float32而不是float64 # 使用iterrows()或itertuples()迭代行 # iterrows()返回(index, Series)对,较慢 for index, row in df.iterrows(): print(row['column']) # itertuples()返回命名元组,更快 for row in df.itertuples(): print(row.column) # 避免在循环中增长DataFrame # 不好的做法 result = pd.DataFrame() for i in range(100): temp = pd.DataFrame({'x': [i], 'y': [i**2]}) result = pd.concat([result, temp]) # 好的做法 data = {'x': [], 'y': []} for i in range(100): data['x'].append(i) data['y'].append(i**2) result = pd.DataFrame(data) # 使用向量化操作 # 不好的做法 df['new_column'] = 0 for i in range(len(df)): df.loc[i, 'new_column'] = df.loc[i, 'column1'] * df.loc[i, 'column2'] # 好的做法 df['new_column'] = df['column1'] * df['column2'] # 使用eval()进行表达式求值 df.eval('new_column = column1 * column2', inplace=True) # 使用query()进行高效查询 result = df.query('column1 > 5 and column2 < 10') 

复杂操作

pandas提供了一些复杂但强大的操作,可以简化数据分析任务。

# 使用apply进行复杂操作 def complex_operation(row): if row['column1'] > 5: return row['column2'] * 2 else: return row['column2'] / 2 df['new_column'] = df.apply(complex_operation, axis=1) # 使用transform进行组内操作 df['group_mean'] = df.groupby('category')['value'].transform('mean') df['deviation'] = df['value'] - df['group_mean'] # 使用rolling进行滚动操作 df['rolling_mean'] = df['value'].rolling(window=7).mean() df['rolling_std'] = df['value'].rolling(window=7).std() # 使用expanding进行扩展操作 df['expanding_mean'] = df['value'].expanding().mean() df['expanding_max'] = df['value'].expanding().max() # 使用pct_change计算百分比变化 df['pct_change'] = df['value'].pct_change() # 使用diff计算差分 df['diff'] = df['value'].diff() # 使用rank计算排名 df['rank'] = df['value'].rank() # 使用cut进行分箱 df['bin'] = pd.cut(df['value'], bins=5, labels=['Very Low', 'Low', 'Medium', 'High', 'Very High']) # 使用qcut进行基于分位数的分箱 df['quantile_bin'] = pd.qcut(df['value'], q=5, labels=['Q1', 'Q2', 'Q3', 'Q4', 'Q5']) # 使用str方法进行字符串操作 df['column'] = df['column'].str.lower() # 转换为小写 df['column'] = df['column'].str.upper() # 转换为大写 df['column'] = df['column'].str.strip() # 去除首尾空格 df['column'] = df['column'].str.replace('old', 'new') # 替换字符串 df['column'] = df['column'].str.split(' ') # 分割字符串 df['column'] = df['column'].str.contains('pattern') # 检查是否包含模式 df['column'] = df['column'].str.startswith('prefix') # 检查是否以特定前缀开头 df['column'] = df['column'].str.endswith('suffix') # 检查是否以特定后缀结尾 

实战案例

现在,让我们通过一个完整的实战案例来展示pandas数据分析的流程。我们将使用一个公开的数据集,从数据导入到最终的可视化,展示整个数据分析过程。

案例背景

假设我们是一家零售公司的数据分析师,我们需要分析销售数据,以了解销售趋势、产品表现和客户行为,并为公司提供决策支持。

数据导入与初步探索

首先,我们导入必要的库,并加载数据:

import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 加载数据 # 假设我们有一个包含销售数据的CSV文件 df = pd.read_csv('sales_data.csv') # 查看数据的前几行 print(df.head()) # 查看数据的基本信息 print(df.info()) # 查看数据的统计摘要 print(df.describe()) # 查看数据的形状 print(f"数据集包含 {df.shape[0]} 行和 {df.shape[1]} 列") 

数据清洗

接下来,我们进行数据清洗,处理缺失值、重复值和异常值:

# 检查缺失值 print(df.isnull().sum()) # 处理缺失值 # 对于数值列,使用均值填充 numeric_columns = df.select_dtypes(include=[np.number]).columns for column in numeric_columns: df[column].fillna(df[column].mean(), inplace=True) # 对于分类列,使用众数填充 categorical_columns = df.select_dtypes(include=['object']).columns for column in categorical_columns: df[column].fillna(df[column].mode()[0], inplace=True) # 检查重复值 print(f"数据集中有 {df.duplicated().sum()} 个重复行") # 删除重复值 df.drop_duplicates(inplace=True) # 处理异常值 # 使用IQR方法检测和处理异常值 for column in numeric_columns: Q1 = df[column].quantile(0.25) Q3 = df[column].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR # 替换异常值为边界值 df[column] = np.where(df[column] < lower_bound, lower_bound, df[column]) df[column] = np.where(df[column] > upper_bound, upper_bound, df[column]) # 转换数据类型 # 假设我们有一个日期列 df['date'] = pd.to_datetime(df['date']) # 假设我们有一个分类列,但被存储为字符串 df['category'] = df['category'].astype('category') 

数据分析与可视化

现在,我们可以开始分析数据并创建可视化:

# 分析销售趋势 # 按月份分组并计算总销售额 df['month'] = df['date'].dt.to_period('M') monthly_sales = df.groupby('month')['sales_amount'].sum().reset_index() monthly_sales['month'] = monthly_sales['month'].astype(str) # 绘制月度销售趋势图 plt.figure(figsize=(12, 6)) sns.lineplot(x='month', y='sales_amount', data=monthly_sales) plt.title('Monthly Sales Trend') plt.xlabel('Month') plt.ylabel('Sales Amount') plt.xticks(rotation=45) plt.tight_layout() plt.show() # 分析产品表现 # 按产品类别分组并计算总销售额和销售数量 product_performance = df.groupby('product_category').agg({ 'sales_amount': 'sum', 'quantity': 'sum' }).reset_index() # 绘制产品类别销售额柱状图 plt.figure(figsize=(12, 6)) sns.barplot(x='product_category', y='sales_amount', data=product_performance) plt.title('Sales Amount by Product Category') plt.xlabel('Product Category') plt.ylabel('Sales Amount') plt.xticks(rotation=45) plt.tight_layout() plt.show() # 分析客户行为 # 按客户分组并计算总销售额和购买频率 customer_behavior = df.groupby('customer_id').agg({ 'sales_amount': 'sum', 'invoice_id': 'count' }).rename(columns={'invoice_id': 'purchase_frequency'}).reset_index() # 绘制客户总销售额分布直方图 plt.figure(figsize=(12, 6)) sns.histplot(customer_behavior['sales_amount'], bins=30, kde=True) plt.title('Distribution of Customer Total Sales') plt.xlabel('Total Sales Amount') plt.ylabel('Frequency') plt.tight_layout() plt.show() # 分析销售渠道 # 按销售渠道分组并计算总销售额和平均订单价值 channel_performance = df.groupby('sales_channel').agg({ 'sales_amount': 'sum', 'invoice_id': 'count' }).reset_index() channel_performance['avg_order_value'] = channel_performance['sales_amount'] / channel_performance['invoice_id'] # 绘制销售渠道销售额饼图 plt.figure(figsize=(10, 8)) plt.pie(channel_performance['sales_amount'], labels=channel_performance['sales_channel'], autopct='%1.1f%%') plt.title('Sales Amount by Channel') plt.axis('equal') plt.tight_layout() plt.show() # 分析时间模式 # 提取星期几和小时 df['day_of_week'] = df['date'].dt.day_name() df['hour'] = df['date'].dt.hour # 按星期几分组并计算平均销售额 weekday_sales = df.groupby('day_of_week')['sales_amount'].mean().reset_index() # 确保星期几按正确顺序排列 days_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] weekday_sales['day_of_week'] = pd.Categorical(weekday_sales['day_of_week'], categories=days_order, ordered=True) weekday_sales = weekday_sales.sort_values('day_of_week') # 绘制星期几平均销售额柱状图 plt.figure(figsize=(12, 6)) sns.barplot(x='day_of_week', y='sales_amount', data=weekday_sales) plt.title('Average Sales by Day of Week') plt.xlabel('Day of Week') plt.ylabel('Average Sales Amount') plt.xticks(rotation=45) plt.tight_layout() plt.show() # 按小时分组并计算平均销售额 hourly_sales = df.groupby('hour')['sales_amount'].mean().reset_index() # 绘制每小时平均销售额线图 plt.figure(figsize=(12, 6)) sns.lineplot(x='hour', y='sales_amount', data=hourly_sales) plt.title('Average Sales by Hour of Day') plt.xlabel('Hour of Day') plt.ylabel('Average Sales Amount') plt.xticks(range(24)) plt.tight_layout() plt.show() 

高级分析

接下来,我们进行一些更高级的分析:

# 客户细分分析 # 计算每个客户的RFM指标 # Recency: 最近一次购买距离现在多久 # Frequency: 购买频率 # Monetary: 购买总金额 # 假设当前日期是数据集中最后一天 current_date = df['date'].max() # 计算Recency recency = df.groupby('customer_id')['date'].max().reset_index() recency['recency'] = (current_date - recency['date']).dt.days recency = recency[['customer_id', 'recency']] # 计算Frequency frequency = df.groupby('customer_id')['invoice_id'].count().reset_index() frequency.columns = ['customer_id', 'frequency'] # 计算Monetary monetary = df.groupby('customer_id')['sales_amount'].sum().reset_index() monetary.columns = ['customer_id', 'monetary'] # 合并RFM指标 rfm = pd.merge(recency, frequency, on='customer_id') rfm = pd.merge(rfm, monetary, on='customer_id') # 为RFM指标打分(1-5分,5分最好) rfm['r_score'] = pd.qcut(rfm['recency'], 5, labels=[5, 4, 3, 2, 1]) rfm['f_score'] = pd.qcut(rfm['frequency'].rank(method='first'), 5, labels=[1, 2, 3, 4, 5]) rfm['m_score'] = pd.qcut(rfm['monetary'], 5, labels=[1, 2, 3, 4, 5]) # 计算RFM综合得分 rfm['rfm_score'] = rfm['r_score'].astype(str) + rfm['f_score'].astype(str) + rfm['m_score'].astype(str) # 定义客户细分 def segment_customers(df): if df['rfm_score'] in ['555', '554', '544', '545', '454', '455', '445']: return 'Champions' elif df['rfm_score'] in ['543', '444', '435', '355', '354', '345', '344', '335']: return 'Loyal Customers' elif df['rfm_score'] in ['512', '511', '422', '421', '412', '411', '311']: return 'Potential Loyalists' elif df['rfm_score'] in ['552', '551', '542', '541', '533', '532', '531', '452', '451', '442', '441', '431', '453', '433', '432', '423', '353', '352', '351', '342', '341', '333', '323']: return 'New Customers' elif df['rfm_score'] in ['525', '524', '523', '522', '521', '515', '514', '513', '425', '424', '413', '414', '415', '315', '314', '313']: return 'Promising' elif df['rfm_score'] in ['255', '254', '245', '244', '253', '252', '243', '242', '235', '234', '225', '224', '153', '152', '145', '143', '142', '135', '134', '133', '125', '124']: return 'Need Attention' elif df['rfm_score'] in ['155', '154', '144', '214', '215', '115', '114']: return 'Cannot Lose Them' elif df['rfm_score'] in ['331', '321', '312', '221', '213', '231', '241', '251']: return 'About To Sleep' elif df['rfm_score'] in ['111', '112', '121', '122', '123', '132', '211', '212', '221']: return 'Lost' else: return 'Others' rfm['segment'] = rfm.apply(segment_customers, axis=1) # 统计各客户细分的数量 segment_counts = rfm['segment'].value_counts().reset_index() segment_counts.columns = ['segment', 'count'] # 绘制客户细分饼图 plt.figure(figsize=(12, 8)) plt.pie(segment_counts['count'], labels=segment_counts['segment'], autopct='%1.1f%%') plt.title('Customer Segments') plt.axis('equal') plt.tight_layout() plt.show() # 产品关联分析 # 创建一个交易-产品矩阵 # 假设我们有一个包含交易ID和产品ID的数据集 transaction_product = df[['invoice_id', 'product_id']].drop_duplicates() # 创建一个矩阵,行是交易ID,列是产品ID,值是是否购买 transaction_product_matrix = transaction_product.groupby(['invoice_id', 'product_id'])['product_id'].count().unstack().fillna(0) transaction_product_matrix = transaction_product_matrix.applymap(lambda x: 1 if x > 0 else 0) # 计算产品之间的关联度 from mlxtend.frequent_patterns import apriori, association_rules # 使用Apriori算法找出频繁项集 frequent_itemsets = apriori(transaction_product_matrix, min_support=0.05, use_colnames=True) # 生成关联规则 rules = association_rules(frequent_itemsets, metric="lift", min_threshold=1) # 按lift值排序 rules = rules.sort_values('lift', ascending=False) # 查看前10条关联规则 print(rules.head(10)) # 绘制关联规则的散点图 plt.figure(figsize=(12, 8)) plt.scatter(rules['support'], rules['confidence'], alpha=0.5) plt.xlabel('Support') plt.ylabel('Confidence') plt.title('Association Rules') plt.tight_layout() plt.show() 

结论与建议

基于我们的分析,我们可以得出以下结论和建议:

  1. 销售趋势

    • 销售额在特定月份有显著增长,可能与季节性因素或营销活动有关。
    • 建议:在销售高峰期增加库存,并在销售低谷期开展促销活动。
  2. 产品表现

    • 某些产品类别的销售额明显高于其他类别。
    • 建议:增加高销售额产品类别的库存,并分析低销售额产品类别的原因,考虑是否需要调整产品组合或营销策略。
  3. 客户行为

    • 大部分客户的总销售额集中在特定范围内。
    • 建议:针对高价值客户提供VIP服务,针对低价值客户提供促销活动,以提高其购买频率和金额。
  4. 销售渠道

    • 不同销售渠道的销售额占比不同。
    • 建议:加大对高销售额渠道的投入,并分析低销售额渠道的改进空间。
  5. 时间模式

    • 销售额在一周中的不同天和一天中的不同小时有显著差异。
    • 建议:在高销售额时段增加营销活动,在低销售额时段提供特别促销。
  6. 客户细分

    • 客户可以分为不同的细分市场,每个细分市场有不同的特征和行为。
    • 建议:针对不同的客户细分市场制定差异化的营销策略,例如为”Champions”客户提供忠诚度计划,为”Need Attention”客户提供特别优惠。
  7. 产品关联

    • 某些产品经常被一起购买。
    • 建议:将这些产品捆绑销售,或在购买一个产品时推荐另一个关联产品,以提高交叉销售和追加销售的机会。

总结与进阶学习路径

通过本文的学习,你已经掌握了pandas数据分析的基础知识和高级技巧,从数据导入、清洗、转换、分析到可视化,全面了解了pandas的功能和应用。但是,数据分析是一个不断发展的领域,持续学习和实践是非常重要的。

总结

本文从pandas的基础概念开始,逐步介绍了pandas的核心数据结构(Series和DataFrame)、数据导入与导出、数据清洗与预处理、数据选择与过滤、数据转换与处理、数据合并与重塑、时间序列数据处理、数据可视化基础以及高级数据分析技巧。通过一个完整的实战案例,我们展示了如何使用pandas进行实际的数据分析工作。

pandas是一个功能强大的数据分析工具,它提供了丰富的函数和方法,可以处理各种类型的数据。掌握pandas不仅可以提高数据分析的效率,还可以为更高级的数据科学任务(如机器学习、深度学习等)打下坚实的基础。

进阶学习路径

为了进一步提高你的pandas技能,以下是一些进阶学习的建议:

  1. 深入学习pandas高级功能

    • 学习pandas的多级索引(MultiIndex)和面板数据(Panel)
    • 掌握pandas的扩展功能,如pandas-profiling、pandas-datareader等
    • 学习pandas的性能优化技巧,如使用eval()、query()等
  2. 学习相关工具和库

    • NumPy:pandas的基础,提供高性能的多维数组对象
    • SciPy:提供科学计算功能,如统计、优化、插值等
    • Scikit-learn:提供机器学习算法和工具
    • Statsmodels:提供统计模型和测试
    • Dask:提供并行计算功能,可以处理大于内存的数据集
  3. 学习大数据处理工具

    • PySpark:Python的Spark API,可以处理大规模数据集
    • Modin:提供pandas API,但使用Dask或Ray作为后端,可以加速pandas操作
    • Vaex:提供类似pandas的API,但可以处理大于内存的数据集
  4. 学习数据可视化高级技巧

    • Matplotlib:Python的基础可视化库
    • Seaborn:基于matplotlib的统计数据可视化库
    • Plotly:提供交互式可视化功能
    • Bokeh:提供交互式可视化功能,特别适合大数据集
    • Altair:提供声明式可视化API
  5. 实践项目

    • 参与Kaggle竞赛:Kaggle是一个数据科学竞赛平台,提供了大量的数据集和问题
    • 分析公开数据集:政府、研究机构和企业经常发布公开数据集,可以用于练习
    • 个人项目:选择你感兴趣的领域,收集数据并进行分析
  6. 学习资源

    • 官方文档:pandas的官方文档是最好的学习资源之一
    • 书籍:《Python for Data Analysis》by Wes McKinney(pandas的创始人)、《利用Python进行数据分析》等
    • 在线课程:Coursera、Udemy、edX等平台提供的数据分析课程
    • 博客和教程:DataCamp、Towards Data Science、Real Python等网站提供大量的pandas教程和技巧
  7. 参与社区

    • Stack Overflow:提问和回答pandas相关问题
    • GitHub:参与pandas和相关库的开发
    • Reddit:r/datascience、r/pandas等社区
    • 本地数据科学聚会:参加本地的数据科学聚会和会议

通过持续学习和实践,你将能够更加熟练地使用pandas进行数据分析,并能够解决更复杂的数据问题。记住,数据分析不仅是一种技术,也是一种思维方式,需要不断练习和思考才能掌握。祝你在数据分析的旅程中取得成功!