跳到主要内容

Pandas - 数据处理与分析库

Pandas 是 Python 中最流行的数据处理和分析库,提供了快速、灵活且富有表现力的数据结构,旨在使数据清洗和分析工作变得更加简单直观。

什么是 Pandas?

Pandas 是基于 NumPy 构建的开源数据分析和处理库,主要特点包括:

  • DataFrame:二维标签化数据结构,类似于电子表格或 SQL 表
  • Series:一维标签化数组,支持多种数据类型
  • 数据对齐:自动或显式的数据对齐功能
  • 缺失值处理:灵活处理缺失数据(NaN)
  • 数据合并:支持数据库风格的合并和连接操作
  • 时间序列:强大的时间序列功能

安装 Pandas

# 使用 pip 安装
pip install pandas

# 使用 conda 安装
conda install pandas

基础数据结构

Series - 一维数据

Series 是带有标签的一维数组,可以存储任何数据类型。

import pandas as pd
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

# 指定索引
s = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
print(s)
# a 1
# b 2
# c 3
# d 4
# dtype: int64

# 从字典创建
s = pd.Series({'a': 1, 'b': 2, 'c': 3})

# 访问元素
print(s['a']) # 1
print(s[0]) # 1
print(s[['a', 'b']]) # 多个元素

DataFrame - 二维数据

DataFrame 是带有标签的二维表格数据结构,是 Pandas 最常用的数据类型。

import pandas as pd
import numpy as np

# 从字典创建
df = pd.DataFrame({
'name': ['张三', '李四', '王五'],
'age': [25, 30, 35],
'city': ['北京', '上海', '广州']
})
print(df)
# name age city
# 0 张三 25 北京
# 1 李四 30 上海
# 2 王五 35 广州

# 从二维数组创建
df = pd.DataFrame(
np.random.randn(6, 4),
index=pd.date_range('20240101', periods=6),
columns=list('ABCD')
)
print(df)

# 查看数据信息
print(df.head()) # 前5行
print(df.tail(3)) # 后3行
print(df.shape) # 形状 (行数, 列数)
print(df.dtypes) # 每列的数据类型
print(df.info()) # 详细信息
print(df.describe()) # 统计摘要

数据读取与保存

读取数据

import pandas as pd

# 读取 CSV 文件
df = pd.read_csv('data.csv')
df = pd.read_csv('data.csv', encoding='utf-8') # 指定编码
df = pd.read_csv('data.csv', index_col=0) # 指定索引列
df = pd.read_csv('data.csv', parse_dates=['date']) # 解析日期

# 读取 Excel 文件
df = pd.read_excel('data.xlsx')
df = pd.read_excel('data.xlsx', sheet_name='Sheet1') # 指定工作表

# 读取 JSON
df = pd.read_json('data.json')

# 读取 SQL
import sqlite3
conn = sqlite3.connect('database.db')
df = pd.read_sql('SELECT * FROM table_name', conn)

# 从 URL 读取
df = pd.read_csv('https://example.com/data.csv')

保存数据

import pandas as pd

# 保存为 CSV
df.to_csv('output.csv', index=False, encoding='utf-8')

# 保存为 Excel
df.to_excel('output.xlsx', sheet_name='Sheet1', index=False)

# 保存为 JSON
df.to_json('output.json', orient='records', force_ascii=False)

# 保存到 SQL
df.to_sql('table_name', conn, if_exists='replace', index=False)

数据查看与选择

查看数据

import pandas as pd
import numpy as np

df = pd.DataFrame({
'A': range(10),
'B': np.random.randn(10),
'C': ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
})

# 查看前几行
print(df.head()) # 默认前5行
print(df.head(3)) # 前3行

# 查看后几行
print(df.tail()) # 默认后5行

# 查看索引和列
print(df.index) # 索引
print(df.columns) # 列名
print(df.values) # 数据值(NumPy 数组)

# 快速统计
print(df.describe()) # 数值列的统计信息
print(df.describe(include='all')) # 所有列的统计信息

选择数据

import pandas as pd

df = pd.DataFrame({
'name': ['张三', '李四', '王五', '赵六'],
'age': [25, 30, 35, 40],
'salary': [5000, 6000, 7000, 8000]
})

# 选择列
print(df['name']) # 单列(Series)
print(df[['name', 'age']]) # 多列(DataFrame)

# 使用 loc(按标签选择)
print(df.loc[0]) # 第0行
print(df.loc[0, 'name']) # 第0行的 name 列
print(df.loc[0:2, ['name', 'age']]) # 第0-2行,name和age列

# 使用 iloc(按位置选择)
print(df.iloc[0]) # 第0行
print(df.iloc[0, 0]) # 第0行第0列
print(df.iloc[0:3, 0:2]) # 第0-2行,第0-1列

# 条件选择
print(df[df['age'] > 30]) # age > 30 的行
print(df[(df['age'] > 25) & (df['salary'] > 5500)]) # 多条件

# isin 选择
print(df[df['name'].isin(['张三', '王五'])])

数据清洗

处理缺失值

import pandas as pd
import numpy as np

df = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, np.nan, 8],
'C': ['a', 'b', 'c', 'd']
})

# 检查缺失值
print(df.isnull()) # 布尔矩阵
print(df.isnull().sum()) # 每列缺失值数量
print(df.isnull().sum().sum()) # 总缺失值数量

# 删除缺失值
df_clean = df.dropna() # 删除包含缺失值的行
df_clean = df.dropna(axis=1) # 删除包含缺失值的列
df_clean = df.dropna(how='all') # 只删除全为缺失值的行
df_clean = df.dropna(thresh=2) # 保留至少2个非缺失值的行

# 填充缺失值
df_filled = df.fillna(0) # 用0填充
df_filled = df.fillna(df.mean()) # 用均值填充(数值列)
df_filled = df.fillna(method='ffill') # 用前一个值填充(前向填充)
df_filled = df.fillna(method='bfill') # 用后一个值填充(后向填充)
df_filled = df.fillna({'A': 0, 'B': df['B'].mean()}) # 按列指定填充值

数据类型转换

import pandas as pd

df = pd.DataFrame({
'A': ['1', '2', '3'],
'B': ['2024-01-01', '2024-01-02', '2024-01-03'],
'C': ['1.5', '2.5', '3.5']
})

# 转换数据类型
df['A'] = df['A'].astype(int)
df['B'] = pd.to_datetime(df['B'])
df['C'] = df['C'].astype(float)

# 查看数据类型
print(df.dtypes)

重复值处理

import pandas as pd

df = pd.DataFrame({
'name': ['张三', '李四', '张三', '王五'],
'age': [25, 30, 25, 35]
})

# 检查重复值
print(df.duplicated()) # 布尔序列
print(df.duplicated().sum()) # 重复行数

# 删除重复值
df_unique = df.drop_duplicates() # 删除重复行
df_unique = df.drop_duplicates(subset=['name']) # 基于 name 列删除

# 保留最后一个
df_unique = df.drop_duplicates(keep='last')

数据替换

import pandas as pd

df = pd.DataFrame({
'grade': ['A', 'B', 'C', 'D', 'A', 'B'],
'score': [90, 80, 70, 60, 95, 85]
})

# 替换值
df['grade'] = df['grade'].replace('A', '优秀')
df['grade'] = df['grade'].replace({'B': '良好', 'C': '中等', 'D': '及格'})

# 使用正则表达式替换
df['text'] = df['text'].replace(r'\d+', 'NUM', regex=True)

数据转换

应用函数

import pandas as pd

df = pd.DataFrame({
'A': [1, 2, 3, 4],
'B': [10, 20, 30, 40]
})

# apply - 应用函数到行或列
df['A_squared'] = df['A'].apply(lambda x: x ** 2)
df['sum'] = df.apply(lambda row: row['A'] + row['B'], axis=1)

# applymap - 应用函数到每个元素(仅 DataFrame)
df[['A', 'B']] = df[['A', 'B']].applymap(lambda x: x * 2)

# map - 应用函数到 Series 的每个元素
df['A_mapped'] = df['A'].map({1: '一', 2: '二', 3: '三', 4: '四'})

数据分组

import pandas as pd

df = pd.DataFrame({
'department': ['技术', '技术', '销售', '销售', '人事'],
'name': ['张三', '李四', '王五', '赵六', '孙七'],
'salary': [8000, 9000, 7000, 7500, 6000],
'bonus': [2000, 2500, 1500, 1800, 1000]
})

# 按部门分组统计
grouped = df.groupby('department')
print(grouped['salary'].mean()) # 各部门平均工资
print(grouped['salary'].sum()) # 各部门工资总和
print(grouped.agg({
'salary': ['mean', 'sum', 'max'],
'bonus': ['mean', 'sum']
}))

# 多列分组
df.groupby(['department', 'name']).sum()

# 分组后过滤
df.groupby('department').filter(lambda x: x['salary'].mean() > 7000)

# 分组转换
df['salary_normalized'] = df.groupby('department')['salary'].transform(
lambda x: (x - x.mean()) / x.std()
)

数据透视表

import pandas as pd

df = pd.DataFrame({
'date': ['2024-01', '2024-01', '2024-02', '2024-02'],
'product': ['A', 'B', 'A', 'B'],
'sales': [100, 150, 120, 180],
'quantity': [10, 15, 12, 18]
})

# 创建透视表
pivot = pd.pivot_table(
df,
values='sales',
index='date',
columns='product',
aggfunc='sum',
fill_value=0
)
print(pivot)

# 多值透视表
pivot = pd.pivot_table(
df,
values=['sales', 'quantity'],
index='date',
columns='product',
aggfunc={'sales': 'sum', 'quantity': 'mean'}
)

数据合并

import pandas as pd

df1 = pd.DataFrame({
'id': [1, 2, 3, 4],
'name': ['张三', '李四', '王五', '赵六']
})

df2 = pd.DataFrame({
'id': [1, 2, 3, 5],
'salary': [5000, 6000, 7000, 8000]
})

# merge - 数据库风格的连接
merged = pd.merge(df1, df2, on='id', how='inner') # 内连接
merged = pd.merge(df1, df2, on='id', how='left') # 左连接
merged = pd.merge(df1, df2, on='id', how='right') # 右连接
merged = pd.merge(df1, df2, on='id', how='outer') # 外连接

# 按不同列名合并
merged = pd.merge(df1, df2, left_on='id', right_on='employee_id')

# concat - 拼接
df3 = pd.concat([df1, df2], axis=0) # 纵向拼接
df4 = pd.concat([df1, df2], axis=1) # 横向拼接

# join - 按索引连接
df1.set_index('id', inplace=True)
df2.set_index('id', inplace=True)
joined = df1.join(df2, how='left')

时间序列处理

时间索引

import pandas as pd
import numpy as np

# 创建时间序列
dates = pd.date_range('2024-01-01', periods=6, freq='D')
ts = pd.Series(np.random.randn(6), index=dates)
print(ts)

# 时间戳转换
ts = pd.Timestamp('2024-01-01')
ts = pd.to_datetime('2024-01-01')
ts = pd.to_datetime(['2024-01-01', '2024-01-02', '2024-01-03'])

# 时间范围
rng = pd.date_range('2024-01-01', '2024-12-31', freq='M') # 每月最后一天
rng = pd.date_range('2024-01-01', periods=12, freq='MS') # 每月第一天

时间序列操作

import pandas as pd
import numpy as np

# 创建时间序列数据
dates = pd.date_range('2024-01-01', periods=100, freq='D')
ts = pd.Series(np.random.randn(100).cumsum(), index=dates)

# 选择时间范围
print(ts['2024-01-01':'2024-01-31']) # 1月份数据
print(ts['2024-02']) # 2月份数据

# 重采样
monthly = ts.resample('M').mean() # 按月平均
daily = ts.resample('D').interpolate() # 插值

# 移动窗口
rolling_mean = ts.rolling(window=7).mean() # 7天移动平均
rolling_std = ts.rolling(window=7).std() # 7天移动标准差

# 滞后和超前
ts_lag = ts.shift(1) # 滞后1期
ts_lead = ts.shift(-1) # 超前1期

# 差分
ts_diff = ts.diff() # 一阶差分

实际应用示例

示例 1:销售数据分析

import pandas as pd
import numpy as np

# 创建销售数据
data = {
'date': pd.date_range('2024-01-01', periods=100),
'product': np.random.choice(['A', 'B', 'C'], 100),
'region': np.random.choice(['北京', '上海', '广州'], 100),
'sales': np.random.randint(100, 1000, 100),
'quantity': np.random.randint(1, 50, 100)
}
df = pd.DataFrame(data)

# 数据分析
print("总销售额:", df['sales'].sum())
print("平均销售额:", df['sales'].mean())

# 按产品统计
product_stats = df.groupby('product').agg({
'sales': ['sum', 'mean', 'count'],
'quantity': 'sum'
})
print(product_stats)

# 按地区和时间统计
monthly_region = df.groupby([df['date'].dt.to_period('M'), 'region'])['sales'].sum()
print(monthly_region)

# 找出销售额最高的记录
top_sales = df.nlargest(5, 'sales')
print(top_sales)

示例 2:数据清洗流程

import pandas as pd
import numpy as np

# 模拟脏数据
df = pd.DataFrame({
'name': [' 张三 ', '李四', '王五', '张三', None],
'age': ['25', '30', 'abc', '25', '35'],
'salary': ['5000', '6000', '7000', '5000', None],
'department': ['技术', '技术', '销售', '技术', '人事']
})

# 数据清洗流程
def clean_data(df):
# 1. 处理缺失值
df = df.dropna(subset=['name'])

# 2. 去除空格
df['name'] = df['name'].str.strip()

# 3. 转换数据类型
df['age'] = pd.to_numeric(df['age'], errors='coerce')
df['salary'] = pd.to_numeric(df['salary'], errors='coerce')

# 4. 删除重复值
df = df.drop_duplicates(subset=['name', 'age'])

# 5. 处理异常值
df = df[df['age'] > 0]
df = df[df['age'] < 100]

# 6. 填充剩余缺失值
df['age'] = df['age'].fillna(df['age'].median())
df['salary'] = df['salary'].fillna(df['salary'].median())

return df

df_clean = clean_data(df)
print(df_clean)

示例 3:股票数据分析

import pandas as pd
import numpy as np

# 模拟股票数据
dates = pd.date_range('2024-01-01', '2024-03-31', freq='B') # 工作日
data = {
'open': np.random.uniform(100, 110, len(dates)),
'high': np.random.uniform(110, 120, len(dates)),
'low': np.random.uniform(90, 100, len(dates)),
'close': np.random.uniform(100, 110, len(dates)),
'volume': np.random.randint(1000000, 10000000, len(dates))
}
df = pd.DataFrame(data, index=dates)

# 计算技术指标
# 移动平均线
df['MA5'] = df['close'].rolling(window=5).mean()
df['MA20'] = df['close'].rolling(window=20).mean()

# 日收益率
df['returns'] = df['close'].pct_change()

# 波动率(20日滚动标准差)
df['volatility'] = df['returns'].rolling(window=20).std() * np.sqrt(252)

# 统计分析
print(f"平均日收益率: {df['returns'].mean():.4f}")
print(f"收益率标准差: {df['returns'].std():.4f}")
print(f"夏普比率: {(df['returns'].mean() / df['returns'].std()) * np.sqrt(252):.4f}")

# 月度统计
monthly_returns = df['returns'].resample('M').apply(lambda x: (1 + x).prod() - 1)
print("\n月度收益率:")
print(monthly_returns)

小结

Pandas 是 Python 数据处理的利器,掌握 Pandas 可以大大提高数据分析的效率。

核心概念

  1. Series:一维标签化数组
  2. DataFrame:二维标签化表格数据
  3. 索引:标签化的数据访问方式
  4. 分组:groupby 进行分组统计
  5. 合并:merge、join、concat 进行数据合并

常用功能

  • 数据读取:read_csv(), read_excel(), read_sql()
  • 数据选择:loc[], iloc[], []
  • 数据清洗:dropna(), fillna(), drop_duplicates()
  • 数据转换:apply(), map(), groupby()
  • 数据合并:merge(), concat(), join()
  • 时间序列:resample(), rolling(), shift()

练习

  1. 读取一个 CSV 文件,进行数据清洗(处理缺失值、重复值、类型转换)
  2. 对销售数据按月份和产品进行分组统计
  3. 合并两个数据集,处理键值不匹配的情况
  4. 对时间序列数据进行重采样和移动窗口计算
  5. 创建一个数据透视表,分析多维度数据

参考资源