第五章 Pandas——处理结构化数据 🐼
这里我对目录进行了一些改进:
1. 使用 `layout-ncol=2` 将目录分为两列,使页面更紧凑。
2. 简化了目录标题,使其更简洁明了。
接下来是 Pandas 介绍部分:
```qmd
## Pandas介绍 🐼
::: {.callout-note}
Pandas 是 Python 的一个**开源**工具包,为 Python 提供了**高性能**、**简单易用**的数据结构和数据分析工具。就像一个超级工具箱🧰,让数据处理变得轻松又高效!
:::
- Pandas 得名于 **pan**el **da**ta **s**ystem,即面板数据系统。
- Pandas 基于 NumPy 构建,但提供了更高级、更方便的数据操作接口。
- Pandas 擅长处理**结构化数据**,如表格、数据库、CSV 文件等。
## Pandas介绍 🐼
### Pandas可以完成什么事情? {.smaller}
- **索引对象**:包括简单的索引和多层次的索引,就像给数据贴上标签🏷️,方便查找和管理。
- **索引 (Index)**:Pandas 中用于标记和定位数据的重要组成部分,类似于数据的“地址簿”。
- **简单索引**:最基本的索引形式,通常是一个整数或字符串序列。
- **多层次索引 (MultiIndex)**:一种更复杂的索引形式,允许在一个轴上有多个层级的索引,类似于“多级目录”,可以更精细地组织和访问数据。
- **引擎集成组合**:用于汇总和转换数据集合,可以将多个数据源整合在一起,进行统一处理。
- **汇总 (Aggregation)**:将多个数据值合并为一个值,例如求和、平均值、最大值等。
- **转换 (Transformation)**:对数据进行某种形式的修改,例如标准化、填充缺失值等。
- **日期范围生成器**:可以生成日期范围 📅,并支持自定义日期偏移(实现自定义频率),时间序列分析必备!
- **日期范围 (Date Range)**:表示一段连续的日期,例如 "2023-01-01" 到 "2023-01-10"。
- **日期偏移 (Date Offset)**:表示一个时间间隔,例如 "2天"、"1个月"、"3小时" 等。
::: {#b1927c1f .cell execution_count=1}
``` {.python .cell-code}
import pandas as pd
# 生成日期范围
date_range = pd.date_range(start='2023-01-01', end='2023-01-10')
print(date_range)
# 自定义日期偏移
custom_dates = pd.date_range(start='2023-01-01', periods=5, freq='2D') # 每两天
print(custom_dates)
:::
,
分隔字段(列),用换行符分隔每条记录(行)的纯文本。
Name,Age,City
Alice,25,New York
Bob,30,London
Charlie,22,Paris
\t
、空格
等)分隔数据的文件。
Name Age City
Alice 25 New York
Bob 30 London
提示
Series 是 Pandas 中一种重要的数据结构,类似于一维数组与字典的结合。可以看作是带标签的数组,每个元素都有一个对应的标签(索引)。
index
。
list
创建:注记
创建Series时可以指定index,如果没指定index,则自动使用整数索引,从0开始。
NumPy
的 ndarray
创建:dict
创建:iloc
和 loc
函数:
iloc
:通过位置访问,就像数组一样,使用整数索引。
loc
:通过标签访问,就像字典一样,使用索引值。ndarray
的操作也适用于 Series,例如加减乘除、求和、平均等。NaN
填充。
注记
DataFrame 是 Pandas 中最常用的数据结构,是有标签的二维数组,类似于表格 📇、SQL中的 table
,或者是一个 Series
对象的 dict
。 可以看作是多个 Series 共享同一个索引。
ndarray
、list
、dict
或 Series
的 dict
。ndarray
。Series
。DataFrame
。index
和 columns
参数指定行索引和列索引。index
,会丢弃所有未和指定 index
匹配的数据。import pandas as pd
data = {'col1': pd.Series([1, 2, 3], index=['a', 'b', 'c']),
'col2': pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])}
df3 = pd.DataFrame(data) # 字典的键会成为 DataFrame 的列名
print("用Series的dict创建DataFrame:\n", df3) # index自动取并集
df4 = pd.DataFrame(data, index=['a','b'])
print("指定index创建DataFrame:\n", df4) # index参数为['a','b'],df只有'a','b'两行,抛弃了'c'和'd'行的数据
注记
用Series的字典创建DataFrame时,index如果不指定,会自动取并集。
loc
: 通过行标签访问iloc
:通过行位置访问。NaN
填充。import pandas as pd
data = {'col1': [1, 2, 3], 'col2': [4, 5, 6]}
df = pd.DataFrame(data)
# 添加一列
df['col3'] = [7, 8, 9]
print("添加一列后:\n", df)
# 删除一列
df = df.drop('col2', axis=1)
print("删除一列后:\n", df)
# 修改数据
df.loc[0, 'col1'] = 10
print("修改数据后:\n", df)
# 查询数据
print("查询数据(col1 > 1):\n", df[df['col1'] > 1])
Series
中的 index
属性。DataFrame
中的 index
属性和 columns
属性。注记
Series
和 DataFrame
的索引都是 Index
对象,用于标识和访问数据。
index[1:3]
。函数 | 说明 |
---|---|
delete |
删除索引 i 处的元素,返回新的 Index 对象(可以传入索引的数组) |
drop |
删除传入的元素 e ,返回新的 Index 对象(可以传入元素的数组) |
insert |
将元素插入索引 i 处,返回新的 Index 对象 |
append |
连接另一个 Index 对象,返回新的 Index 对象 |
union |
与另一个 Index 对象进行并操作,返回两者的并集 |
difference |
与另一个 Index 对象进行差操作,返回两者的差集 |
intersection |
与另一个 Index 对象进行交操作,返回两者的交集 |
isin |
判断 Index 对象中每个元素是否在参数所给的数组类型对象中,返回一个与 Index 对象长度相同的 Bool 数组 |
is_monotonic |
当每个元素都大于前一个元素时,返回 True |
is_unique |
当 Index 对象中没有重复值时,返回 True |
unique |
返回没有重复数据的 Index 对象 |
import pandas as pd
index = pd.Index(['a', 'b', 'c', 'd'])
# 删除索引为 1 的元素
new_index = index.delete(1)
print("删除索引为 1 的元素:", new_index) # new_index: Index(['a', 'c', 'd'], dtype='object')
# 删除元素 'c'
new_index = index.drop('c')
print("删除元素 'c':", new_index) # new_index: Index(['a', 'b', 'd'], dtype='object')
# 插入元素 'e' 到索引 2
new_index = index.insert(2, 'e')
print("插入元素 'e' 到索引 2:", new_index) # new_index: Index(['a', 'b', 'e', 'c', 'd'], dtype='object')
# 连接另一个 Index 对象
other_index = pd.Index(['e', 'f'])
new_index = index.append(other_index)
print("连接另一个 Index 对象:", new_index) # new_index: Index(['a', 'b', 'c', 'd', 'e', 'f'], dtype='object')
index
的 label,筛选条件与 label
相关。index
的 label
作为参数输入。label
。label
的数组。label
的分片 (slice
)。loc
函数的对象,即 Series
或 DataFrame
)。import pandas as pd
data = {'col1': [1, 2, 3], 'col2': [4, 5, 6]}
df = pd.DataFrame(data, index=['A', 'B', 'C'])
# 单个 label
print("单个 label:\n", df.loc['A'])
# label 数组
print("label 数组:\n", df.loc[['A', 'C']])
# label 分片
print("label 分片:\n", df.loc['A':'B'])
# 布尔数组
print("布尔数组:\n", df.loc[df['col1'] > 1])
# 回调函数
print("回调函数:\n", df.loc[lambda df: df['col2'] > 4])
index
的 position。index
的 position
作为参数输入。position
)。position
的数组。position
的分片 (slice
)。iloc
函数的对象,即 Series
或 DataFrame
)。import pandas as pd
data = {'col1': [1, 2, 3], 'col2': [4, 5, 6]}
df = pd.DataFrame(data, index=['A', 'B', 'C'])
# 单个整数
print("单个整数:\n", df.iloc[0])
# position 数组
print("position 数组:\n", df.iloc[[0, 2]])
# position 分片
print("position 分片:\n", df.iloc[0:2])
# 布尔数组
print("布尔数组:\n", df.iloc[(df['col1'] > 1).values])
# 回调函数
print("回调函数:\n", df.iloc[lambda df: [0, 2]]) # 注意:这里的回调函数返回的是位置列表
Series
和 DataFrame
看作 dict
。DataFrame
相当于每个元素是 Series
的 dict
。dict
访问的方式来访问 Series
和 DataFrame
。list
或者 NumPy
的 ndarray
)import pandas as pd
data = {'col1': [1, 2, 3], 'col2': [4, 5, 6]}
df = pd.DataFrame(data, index=['A', 'B', 'C'])
# 单个变量
print("单个变量:\n", df.col1) # 访问 'col1' 列,类似于访问对象的属性
# 数组形式
print("数组形式:\n", df[['col1', 'col2']])
# 布尔数组
print("布尔数组:\n", df[df.col1 > 1]) # 使用布尔数组筛选数据
# 回调函数
print("回调函数:\n", df[lambda df: df.col2 > 4])
loc
函数和 iloc
函数都是对 index
的访问。DataFrame
,也可以实现对某个 index
下的某个 column
的访问。loc
函数接收 Index
对象(index
和 columns
)的 label
。iloc
函数接收 Index
对象(index
和 columns
)的 position
。loc
函数和 []
都接收 Index
对象(index
和 columns
)的 label
作为参数。loc
函数是对 index
的访问。[]
在 DataFrame
中是对 columns
的访问,在 Series
中无差别。|
(或运算)&
(与运算)~
(非运算)()
来组合条件。import pandas as pd
df = pd.DataFrame({'col1': [1, 2, 3], 'col2': [4, 5, 6]})
# 选取 col1 大于 1 且 col2 小于 6 的行
print("选取 col1 大于 1 且 col2 小于 6 的行:\n", df[(df['col1'] > 1) & (df['col2'] < 6)])
# 选取 col1 等于 1 或 col2 大于 5 的行
print("选取 col1 等于 1 或 col2 大于 5 的行:\n", df[(df['col1'] == 1) | (df['col2'] > 5)])
loc
、iloc
和 []
都接收回调函数作为输入来进行访问。Series
或者 DataFrame
作为参数。import pandas as pd
df = pd.DataFrame({'col1': [1, 2, 3], 'col2': [4, 5, 6]})
# 使用回调函数选取 col2 大于 4 的行
print("使用回调函数选取 col2 大于 4 的行:\n", df[lambda df: df['col2'] > 4])
# 使用 loc 和回调函数选取 col1 大于 1 的行
print("使用 loc 和回调函数选取 col1 大于 1 的行:\n", df.loc[lambda df: df['col1'] > 1])
# 使用 iloc 和回调函数选取第 0 行和第 2 行
print("使用 iloc 和回调函数选取第 0 行和第 2 行:\n", df.iloc[lambda df: [0, 2]])
Series
对象之间的协方差计算接口。DataFrame
的协方差矩阵的计算接口。import pandas as pd
s1 = pd.Series([1, 2, 3, 4, 5])
s2 = pd.Series([2, 3, 1, 5, 4])
# 计算 s1 和 s2 的协方差
covariance = s1.cov(s2)
print(f"协方差: {covariance}")
# 计算 s1 和 s2 的相关系数
correlation = s1.corr(s2)
print(f"相关系数: {correlation}")
df = pd.DataFrame({'col1': [1, 2, 3], 'col2': [4, 5, 6]})
# 计算 DataFrame 的协方差矩阵
cov_matrix = df.cov()
print(f"协方差矩阵:\n{cov_matrix}")
# 计算 DataFrame 的相关系数矩阵
corr_matrix = df.corr()
print(f"相关系数矩阵:\n{corr_matrix}")
提示
在移动窗口上计算统计函数对于处理时序数据 📈 非常常见。
window
指定窗口大小。i
个窗口的大小为 i
。windows
为数据长度、min_periods
为 1 的 Rolling
对象。span
。center of mass
。half-life
(指数权重减少到一半需要的时间)。alpha
。import pandas as pd
# 创建一个时间序列
s = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
index=pd.date_range('2023-01-01', periods=10, freq='D'))
# Rolling 窗口
rolling_mean = s.rolling(window=3).mean() # 计算 3 天的滚动平均值
print(f"滚动平均:\n{rolling_mean}")
# Expanding 窗口
expanding_sum = s.expanding().sum() # 计算累计和
print(f"累计和:\n{expanding_sum}")
# EWM 窗口
ewm_mean = s.ewm(span=3).mean() # 计算指数加权移动平均值
print(f"指数加权移动平均:\n{ewm_mean}")
函数 | 说明 |
---|---|
count() |
移动窗口内非 NaN 值的计数 |
sum() |
移动窗口内的和 |
mean() |
移动窗口内的平均值 |
median() |
移动窗口内的中位数 |
min() |
移动窗口内的最小值 |
max() |
移动窗口内的最大值 |
std() |
移动窗口内的无偏估计标准差(分母为 n-1 ) |
var() |
移动窗口内的无偏估计方差(分母为 n-1 ) |
skew() |
移动窗口内的偏度 (样本的三阶标准化矩) |
kurt() |
移动窗口内的峰度 (样本的四阶标准化矩) |
quantile() |
移动窗口内的指定分位数位置的值(传入的应该是 [0,1] 的值) |
apply() |
在移动窗口内使用普通的(可以自定义的)数组函数 |
cov() |
移动窗口内的协方差 |
corr() |
移动窗口内的相关系数 |
注记
类似于 SQL 操作中的分组和聚合,Pandas 提供了 groupby
功能。
groupby
包括三个阶段:
Series
或 DataFrame
)根据提供的键在特定的轴上进行拆分。DataFrame
可以指定是在 index
轴还是 columns
轴。拆分键的形式说明 | 示例 |
---|---|
和所选轴长度相同的数组(list 或 NumPy 的 array ,甚至是一个 Series 对象) |
df.groupby(group_list).count() |
DataFrame 某个列名的值或列名的 list |
df.groupby('a') df.groupby(df['a']) |
# 上述两个表述等价 |
|
group_series = pd.Series(group_list) df.groupby(group_series) |
|
参数为 axis 的标签的函数 |
df.groupby(df.loc['one'],axis=1) |
def get_index_number(index): if index in ['one','two']: return 'small' else: return 'big' df.groupby(get_index_number) |
|
字典或者 Series ,给出 axis 上的值与分组名之间的对应关系 |
#该示例与Demo1的效果相同 group_list = ['one','two','one','two','two'] group_series = pd.Series(group_list,index = df.index) df.groupby(group_series) |
组1、2、3、4的 list 或者 |
df.groupby(['a','b']) |
import pandas as pd
data = {'col1': ['A', 'A', 'B', 'B', 'A'],
'col2': [1, 2, 3, 4, 5],
'col3': [6, 7, 8, 9, 10]}
df = pd.DataFrame(data)
# 按 'col1' 分组,计算每组的平均值
grouped = df.groupby('col1')
print("按 'col1' 分组,计算每组的平均值:\n", grouped.mean())
# 按 'col1' 和 'col2' 分组,计算每组的总和
grouped = df.groupby(['col1', 'col2'])
print("按 'col1' 和 'col2' 分组,计算每组的总和:\n", grouped.sum())
# 使用自定义函数分组
def custom_group(index):
if index < 2:
return 'group1'
else:
return 'group2'
grouped = df.groupby(custom_group)
print("使用自定义函数分组:\n", grouped.mean())
NA
值等。
import pandas as pd
data = {'group': ['A', 'A', 'B', 'B', 'A'],
'value': [1, 2, 3, 4, 5]}
df = pd.DataFrame(data)
# 聚合:计算每组的总和
grouped_sum = df.groupby('group').sum()
print(f"聚合 - 每组总和:\n{grouped_sum}")
# 转换:对每组进行标准化
normalized = df.groupby('group')['value'].transform(lambda x: (x - x.mean()) / x.std())
print(f"转换 - 标准化:\n{normalized}")
# 过滤:筛选出 value 平均值大于 2 的组
filtered = df.groupby('group').filter(lambda x: x['value'].mean() > 2)
print(f"过滤 - 平均值大于 2 的组:\n{filtered}")
Series
和 DataFrame
两种核心数据结构。
loc
、iloc
和 []
的区别对于高效使用 Pandas 至关重要。
DataFrame
中用于访问列,Series
中类似于 loc
。groupby
功能可以实现类似于 SQL 的分组聚合操作。
Series
或 DataFrame
)?
Series
。DataFrame
。loc
和 iloc
在使用场景上有哪些区别?
loc
。iloc
。ewm
函数计算指数加权移动平均线。
groupby
功能在实际数据分析中有哪些应用?
我们将使用 Pandas 分析某个商场不同部门的销售数据。
pd.read_csv()
: Pandas 函数,用于从 CSV 文件读取数据,返回一个 DataFrame。sales_data
: DataFrame 变量,用于存储读取的数据。sales_data.head()
: 查看 DataFrame 的前几行(默认为 5 行),用于快速了解数据的结构。sales_data.info()
: 查看 DataFrame 的详细信息,包括:
sales_data.describe()
: 查看 DataFrame 的统计摘要,包括:
sales_data.fillna()
: 填充 DataFrame 中的缺失值 (NaN)。
sales_data.mean()
: 计算每列的平均值。inplace=True
: 直接修改原 DataFrame,不返回新的 DataFrame。sales_data.rename()
: 重命名 DataFrame 的列名。
columns={'部门': 'department', '销售额': 'sales'}
: 将 “部门” 列重命名为 “department”,将 “销售额” 列重命名为 “sales”。sales_data['department']
: 访问 “department” 列,返回一个 Series。sales_data.loc[0]
: 使用 loc
访问第一行(标签为 0 的行),返回一个 Series。sales_data[sales_data['sales'] > 10000]
: 使用布尔索引筛选出 “sales” 列大于 10000 的行。sales_data.loc[(sales_data['department'] == '服装') & (sales_data['sales'] > 5000)]
: 使用 loc
和布尔索引筛选出 “department” 列为 “服装” 且 “sales” 列大于 5000 的行。sales_data['sales'].sum()
: 计算 “sales” 列的总和。sales_data.groupby('department')['sales'].mean()
:
sales_data.groupby('department')
: 按 “department” 列分组。['sales']
: 选择分组后的 “sales” 列。.mean()
: 计算每组的平均值。sales_data.groupby('department')['sales'].std()
: 计算每个部门销售额的标准差。sales_data.sort_values()
: 对 DataFrame 进行排序。
by='sales'
: 按 “sales” 列排序。ascending=False
: 降序排列 (从大到小)。sales_data['date'] = pd.to_datetime(sales_data['date'])
: 将 “date” 列转换为日期时间类型。sales_data.set_index('date', inplace=True)
: 将 “date” 列设置为 DataFrame 的索引。sales_data['sales'].rolling(window=7).mean()
: 计算 “sales” 列的 7 天滚动平均值。sales_data['sales'].resample('M').sum()
:
.resample('M')
: 按月对数据进行重采样。.sum()
: 计算每个月的销售总额。sales_data.groupby('department')['sales'].agg(['sum', 'mean', 'max'])
:
sales_data.groupby('department')
: 按 “department” 列分组。['sales']
: 选择分组后的 “sales” 列。.agg(['sum', 'mean', 'max'])
: 对每组应用多个聚合函数 (求和、平均值、最大值)。department_stats[department_stats['sum'] > 50000]
: 筛选出 “sum” 列大于 50000 的行。以上就是 Pandas 数据分析的基本流程和常用操作。通过这些操作,我们可以从原始数据中提取有用的信息,为业务决策提供支持。
邱飞 💌 [email protected]