文章

Pandas

pandas的一些基础语法

Pandas

Pandas 基本语法总结

Pandas 是 Python 中常用的数据分析库,主要用于处理表格数据。

Pandas 中最重要的两种数据结构是:

1
2
Series:一维带标签的数据
DataFrame:二维表格数据

在机器学习中,Pandas 经常用于读取数据、查看数据、筛选样本、处理缺失值、构造特征以及划分特征和标签。

一般导入方式为:

1
import pandas as pd

这里的 pdpandas 的简写,之后可以通过 pd.xxx() 调用 Pandas 中的函数。

通常也会同时导入 NumPy:

1
2
import numpy as np
import pandas as pd

创建 Series

Series 可以理解为一列带有索引的数据。

从列表创建 Series

1
2
s = pd.Series([80, 90, 75])
print(s)

输出:

1
2
3
4
0    80
1    90
2    75
dtype: int64

左边是索引,右边是数据。


指定索引

1
2
3
4
5
6
s = pd.Series(
    [80, 90, 75],
    index=["张三", "李四", "王五"]
)

print(s)

输出:

1
2
3
4
张三    80
李四    90
王五    75
dtype: int64

通过索引可以取得对应的数据:

1
s["李四"]

结果为:

1
90

创建 DataFrame

DataFrame 可以理解为一张带有行索引和列名的二维表格。

从字典创建 DataFrame

1
2
3
4
5
6
7
8
9
data = {
    "name": ["张三", "李四", "王五", "赵六"],
    "age": [18, 20, 19, 21],
    "score": [85.0, 92.0, 76.0, 88.0],
    "city": ["北京", "上海", "北京", "广州"]
}

df = pd.DataFrame(data)
print(df)

输出:

1
2
3
4
5
  name  age  score city
0   张三   18   85.0   北京
1   李四   20   92.0   上海
2   王五   19   76.0   北京
3   赵六   21   88.0   广州

其中:

1
2
0、1、2、3                  → 行索引
name、age、score、city      → 列名

从二维列表创建 DataFrame

1
2
3
4
5
6
7
8
df = pd.DataFrame(
    [
        ["张三", 18, 85.0],
        ["李四", 20, 92.0],
        ["王五", 19, 76.0]
    ],
    columns=["name", "age", "score"]
)

columns 用于指定每一列的列名。


查看数据

查看前几行

1
df.head()

默认查看前 5 行。

查看前 3 行:

1
df.head(3)

查看后几行

1
df.tail()

默认查看最后 5 行。

查看最后 2 行:

1
df.tail(2)

查看形状

1
df.shape

如果结果为:

1
(1000, 20)

表示:

1
2
1000 行数据
20 列数据

通常一行代表一个样本,一列代表一个特征或标签。


查看列名和索引

1
2
df.columns
df.index

把所有列名转换成普通列表:

1
df.columns.tolist()

查看数据类型

1
df.dtypes

可能得到:

1
2
3
4
5
name      object
age        int64
score    float64
city      object
dtype: object

常见的数据类型包括:

1
2
3
4
5
6
int64       整数
float64     浮点数
object      字符串或混合类型
bool        布尔值
datetime64  日期时间
category    分类数据

查看整体信息

1
df.info()

info() 可以查看:

1
2
3
4
5
数据行数和列数
每一列的名称
每一列的非空数量
每一列的数据类型
占用的内存

它是拿到一个新数据集后最常用的检查方法之一。


查看统计信息

1
df.describe()

默认统计数值列,并给出:

1
2
3
4
5
6
7
8
count    非空数据数量
mean     平均值
std      标准差
min      最小值
25%      下四分位数
50%      中位数
75%      上四分位数
max      最大值

如果希望同时查看字符串列:

1
df.describe(include="all")

选择列

选择一列

1
df["score"]

结果是一个 Series

也可以写成:

1
df.score

但是更推荐使用:

1
df["score"]

因为这种写法不受空格、特殊字符和方法重名的影响。


选择多列

1
df[["name", "score"]]

注意需要使用两层中括号:

1
2
外层中括号:从 DataFrame 中选择数据
内层中括号:列名组成的列表

结果仍然是一个 DataFrame


使用 loc 选择数据

loc 根据行标签和列名选择数据。

基本格式为:

1
df.loc[, ]

选择一行

1
df.loc[0]

表示选择索引标签为 0 的行。


选择指定行和列

1
df.loc[0, "name"]

结果为:

1
张三

选择多行多列:

1
df.loc[[0, 2], ["name", "score"]]

使用标签切片

1
df.loc[0:2, "name":"score"]

需要注意,loc 的标签切片通常包含结束位置。

因此:

1
df.loc[0:2]

会选择索引标签 012 对应的三行。


使用 iloc 选择数据

iloc 根据整数位置选择数据,使用方式和 NumPy 索引类似。

基本格式为:

1
df.iloc[行位置, 列位置]

选择一个元素

1
df.iloc[0, 2]

表示选择第 1 行、第 3 列的数据。

Python 的位置索引从 0 开始。


使用位置切片

1
df.iloc[:3, :2]

表示:

1
2
选择前 3 行
选择前 2 列

与普通 Python 切片相同,iloc 切片不包含结束位置。


条件筛选

单个条件

筛选成绩不低于 85 分的数据:

1
df[df["score"] >= 85]

其中:

1
df["score"] >= 85

会生成一个布尔值 Series

1
2
3
4
5
0     True
1     True
2    False
3     True
Name: score, dtype: bool

再用这个布尔值 Series 从原表中选择满足条件的行。


多个条件

筛选年龄不低于 19 岁并且成绩不低于 85 分的数据:

1
df[(df["age"] >= 19) & (df["score"] >= 85)]

筛选来自北京或者上海的数据:

1
df[(df["city"] == "北京") | (df["city"] == "上海")]

其中:

1
2
3
&    并且
|    或者
~    取反

每个条件都需要使用小括号括起来。

不能直接使用 Python 的 andor 连接两个 Series 条件。


isin

筛选某一列属于多个指定值的数据:

1
df[df["city"].isin(["北京", "上海"])]

排除这些值:

1
df[~df["city"].isin(["北京", "上海"])]

between

筛选成绩在 80 到 90 分之间的数据:

1
df[df["score"].between(80, 90)]

默认包含 80 和 90。


query

也可以使用字符串表达式筛选:

1
df.query("age >= 19 and score >= 85")

如果需要引用 Python 变量,可以在变量名前加 @

1
2
min_score = 85
df.query("score >= @min_score")

修改和新增列

修改一列

1
df["score"] = df["score"] + 5

这会给 score 列的每一个值加 5。


新增一列

1
df["passed"] = df["score"] >= 60

结果中的 passed 列为布尔值:

1
2
True     及格
False    不及格

根据现有列计算新特征:

1
df["score_ratio"] = df["score"] / 100

使用 assign

assign() 会返回一个新的 DataFrame

1
2
3
4
new_df = df.assign(
    passed=df["score"] >= 60,
    score_ratio=df["score"] / 100
)

原来的 df 不会被修改。


根据条件赋值

把不及格成绩统一改为 60:

1
df.loc[df["score"] < 60, "score"] = 60

推荐使用 loc 同时指定行和列,避免链式赋值问题。


删除数据

删除列

1
df = df.drop(columns=["city"])

删除多列:

1
df = df.drop(columns=["city", "age"])

删除行

根据索引标签删除行:

1
df = df.drop(index=[0, 2])

原地修改

部分 Pandas 方法支持:

1
df.drop(columns=["city"], inplace=True)

inplace=True 表示直接修改原对象。

为了让数据处理步骤更清晰,也可以不使用 inplace,而是把结果重新赋值:

1
df = df.drop(columns=["city"])

处理缺失值

缺失值通常显示为:

1
2
3
NaN
None
NaT

检查缺失值

1
df.isna()

统计每一列的缺失值数量:

1
df.isna().sum()

统计整个表的缺失值总数:

1
df.isna().sum().sum()

isnull()isna() 的作用相同:

1
df.isnull()

删除缺失值

删除含有缺失值的行:

1
df = df.dropna()

只在指定列缺失时删除该行:

1
df = df.dropna(subset=["score"])

删除所有值都缺失的行:

1
df = df.dropna(how="all")

填充缺失值

使用固定值填充:

1
df["city"] = df["city"].fillna("未知")

使用平均值填充数值列:

1
df["score"] = df["score"].fillna(df["score"].mean())

使用中位数填充:

1
df["age"] = df["age"].fillna(df["age"].median())

在存在极端值时,中位数通常比平均值更加稳定。

使用前一个有效值填充:

1
df["score"] = df["score"].ffill()

使用后一个有效值填充:

1
df["score"] = df["score"].bfill()

处理重复值

检查重复行

1
df.duplicated()

统计重复行数量:

1
df.duplicated().sum()

删除重复行

1
df = df.drop_duplicates()

根据指定列判断重复:

1
df = df.drop_duplicates(subset=["name"])

保留最后一次出现的数据:

1
df = df.drop_duplicates(subset=["name"], keep="last")

修改数据类型

使用 astype

1
2
3
df["age"] = df["age"].astype(int)
df["score"] = df["score"].astype(float)
df["city"] = df["city"].astype("category")

如果数据中存在无法转换的字符串,直接使用 astype 会报错。


使用 to_numeric

1
df["score"] = pd.to_numeric(df["score"], errors="coerce")

其中:

1
errors="coerce"

表示把无法转换为数值的数据变成 NaN,之后可以再统一处理缺失值。


字符串处理

Pandas 可以通过 .str 对一列字符串进行处理。

去除字符串两端的空格:

1
df["name"] = df["name"].str.strip()

转换为小写:

1
df["city"] = df["city"].str.lower()

判断是否包含指定文字:

1
df[df["name"].str.contains("", na=False)]

替换字符串:

1
df["city"] = df["city"].str.replace("", "", regex=False)

拆分字符串:

1
2
3
4
5
df[["province", "city"]] = df["address"].str.split(
    "-",
    n=1,
    expand=True
)

日期时间处理

把字符串转换为日期时间:

1
df["date"] = pd.to_datetime(df["date"])

如果可能存在错误数据:

1
df["date"] = pd.to_datetime(df["date"], errors="coerce")

提取日期信息:

1
2
3
4
df["year"] = df["date"].dt.year
df["month"] = df["date"].dt.month
df["day"] = df["date"].dt.day
df["weekday"] = df["date"].dt.dayofweek

其中:

1
2
dayofweek 为 0 表示星期一
dayofweek 为 6 表示星期日

计算两个日期之间的时间差:

1
df["days"] = (df["end_date"] - df["start_date"]).dt.days

排序

按值排序

按照成绩从小到大排序:

1
df.sort_values(by="score")

按照成绩从大到小排序:

1
df.sort_values(by="score", ascending=False)

按照多列排序:

1
2
3
4
df.sort_values(
    by=["city", "score"],
    ascending=[True, False]
)

表示先按照城市升序排列,同一城市内再按照成绩降序排列。


按索引排序

1
df.sort_index()

降序排列:

1
df.sort_index(ascending=False)

索引操作

设置索引

1
df = df.set_index("name")

此时 name 列会变成行索引。

保留原来的 name 列:

1
df = df.set_index("name", drop=False)

恢复默认索引

1
df = df.reset_index()

如果不需要保留原索引:

1
df = df.reset_index(drop=True)

经过筛选或删除行以后,经常使用:

1
df = df.reset_index(drop=True)

重新生成从 0 开始的连续索引。


常用统计方法

1
2
3
4
5
6
7
8
df["score"].sum()       # 求和
df["score"].mean()      # 平均值
df["score"].median()    # 中位数
df["score"].std()       # 标准差
df["score"].min()       # 最小值
df["score"].max()       # 最大值
df["score"].count()     # 非空值数量
df["score"].nunique()   # 不同值的数量

查找最大值所在的索引:

1
df["score"].idxmax()

查找最小值所在的索引:

1
df["score"].idxmin()

value_counts

统计每一个值出现的次数:

1
df["city"].value_counts()

统计比例:

1
df["city"].value_counts(normalize=True)

同时统计缺失值:

1
df["city"].value_counts(dropna=False)

相关系数

计算数值列之间的相关系数:

1
df.corr(numeric_only=True)

只查看某一列与其他列的相关系数:

1
df.corr(numeric_only=True)["score"]

相关系数接近 1 表示强正相关,接近 -1 表示强负相关,接近 0 表示线性相关性较弱。


map 和 apply

使用 map 替换一列中的值

1
2
3
4
5
6
7
city_code = {
    "北京": 0,
    "上海": 1,
    "广州": 2
}

df["city_code"] = df["city"].map(city_code)

map() 常用于对 Series 中的每一个元素进行映射。

如果原数据中存在字典没有包含的值,映射结果会是 NaN


使用 apply 调用函数

1
2
3
4
5
6
7
8
9
def score_level(score):
    if score >= 90:
        return "优秀"
    elif score >= 60:
        return "及格"
    else:
        return "不及格"

df["level"] = df["score"].apply(score_level)

也可以使用匿名函数:

1
df["score_ratio"] = df["score"].apply(lambda x: x / 100)

简单的数值运算应优先使用向量化写法:

1
df["score_ratio"] = df["score"] / 100

这种写法通常比 apply() 更简洁、速度也更快。


分组统计

groupby() 用于按照某一列或多列对数据进行分组。

按一列分组

计算每个城市的平均成绩:

1
df.groupby("city")["score"].mean()

如果希望结果仍然是 DataFrame

1
df.groupby("city", as_index=False)["score"].mean()

同时计算多个统计量

1
2
3
result = df.groupby("city")["score"].agg(
    ["count", "mean", "max", "min"]
)

使用更清楚的列名:

1
2
3
4
5
result = df.groupby("city").agg(
    student_count=("name", "count"),
    average_score=("score", "mean"),
    highest_score=("score", "max")
).reset_index()

按多列分组

1
df.groupby(["city", "gender"])["score"].mean()

表示先按照城市分组,再在每个城市中按照性别分组。


使用 transform

为每一行添加所在城市的平均成绩:

1
df["city_mean"] = df.groupby("city")["score"].transform("mean")

agg() 通常会减少数据行数,transform() 返回的结果则与原数据行数相同,因此可以直接作为新列加入原表。


合并数据

concat

按行拼接两个结构相同的表:

1
2
3
4
5
all_data = pd.concat(
    [train_df, test_df],
    axis=0,
    ignore_index=True
)

其中:

1
2
axis=0             按行拼接
ignore_index=True  重新生成连续索引

按列拼接:

1
new_df = pd.concat([feature_df, label_df], axis=1)

merge

假设有两张表:

1
2
3
4
5
6
7
8
9
students = pd.DataFrame({
    "student_id": [1, 2, 3],
    "name": ["张三", "李四", "王五"]
})

scores = pd.DataFrame({
    "student_id": [1, 2, 4],
    "score": [85, 92, 78]
})

按照 student_id 合并:

1
2
3
4
5
6
result = pd.merge(
    students,
    scores,
    on="student_id",
    how="inner"
)

how 的常见取值:

1
2
3
4
inner    只保留两个表中都存在的键
left     保留左表中的所有键
right    保留右表中的所有键
outer    保留两个表中的所有键

左连接:

1
2
3
4
5
6
result = pd.merge(
    students,
    scores,
    on="student_id",
    how="left"
)

如果两个表用于连接的列名不同:

1
2
3
4
5
6
7
result = pd.merge(
    left_df,
    right_df,
    left_on="student_id",
    right_on="id",
    how="left"
)

数据透视表

pivot_table

按照城市统计不同性别的平均成绩:

1
2
3
4
5
6
7
table = pd.pivot_table(
    df,
    values="score",
    index="city",
    columns="gender",
    aggfunc="mean"
)

其中:

1
2
3
4
values     需要统计的数值
index      行方向的分组字段
columns    列方向的分组字段
aggfunc    聚合方法

crosstab

统计两个分类变量的组合次数:

1
pd.crosstab(df["city"], df["gender"])

计算每一行的比例:

1
2
3
4
5
pd.crosstab(
    df["city"],
    df["gender"],
    normalize="index"
)

读取文件

读取 CSV 文件

1
df = pd.read_csv("data.csv")

常用参数:

1
2
3
4
5
6
df = pd.read_csv(
    "data.csv",
    encoding="utf-8",
    usecols=["name", "age", "score"],
    na_values=["?", "unknown"]
)

其中:

1
2
3
encoding    文件编码
usecols     只读取指定列
na_values   把指定内容识别为缺失值

如果 CSV 文件没有表头:

1
2
3
4
5
df = pd.read_csv(
    "data.csv",
    header=None,
    names=["feature_1", "feature_2", "label"]
)

读取大型文件时,可以分块读取:

1
2
for chunk in pd.read_csv("large_data.csv", chunksize=10000):
    print(chunk.shape)

读取 Excel 文件

1
df = pd.read_excel("data.xlsx")

读取指定工作表:

1
2
3
4
df = pd.read_excel(
    "data.xlsx",
    sheet_name="Sheet1"
)

读取其他常见格式

1
2
df = pd.read_json("data.json")
df = pd.read_parquet("data.parquet")

Parquet 通常比 CSV 更节省空间,并且能够保存列的数据类型,适合存储较大的表格数据。


保存文件

保存为 CSV:

1
2
3
4
5
df.to_csv(
    "result.csv",
    index=False,
    encoding="utf-8-sig"
)

其中:

1
2
index=False          不保存行索引
encoding="utf-8-sig" 方便使用 Excel 打开中文 CSV

保存为 Excel:

1
df.to_excel("result.xlsx", index=False)

保存为 Parquet:

1
df.to_parquet("result.parquet", index=False)

Pandas 与 NumPy 相互转换

DataFrame 转换为 NumPy 数组

1
array = df.to_numpy()

选择特征列后再转换:

1
x = df[["age", "score"]].to_numpy()

选择标签列:

1
y = df["passed"].to_numpy()

NumPy 数组转换为 DataFrame

1
2
3
4
5
6
7
8
9
10
array = np.array([
    [18, 85],
    [20, 92],
    [19, 76]
])

df = pd.DataFrame(
    array,
    columns=["age", "score"]
)

在机器学习中准备特征和标签

假设数据中 target 是需要预测的标签,其余列是输入特征:

1
2
x = df.drop(columns=["target"])
y = df["target"]

这里:

1
2
x    特征
y    标签

如果模型只需要指定的几个特征:

1
2
3
4
feature_columns = ["age", "income", "score"]

x = df[feature_columns]
y = df["target"]

划分训练集和测试集:

1
2
3
4
5
6
7
8
9
from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(
    x,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

其中:

1
2
3
test_size=0.2     20% 的数据作为测试集
random_state=42   固定随机结果,便于复现实验
stratify=y        让训练集和测试集中的类别比例接近原数据

stratify=y 一般用于分类问题,回归问题通常不使用。


分类变量编码

使用 map 编码二分类

1
2
3
4
df["gender_code"] = df["gender"].map({
    "": 0,
    "": 1
})

使用 get_dummies 独热编码

1
2
3
4
5
encoded_df = pd.get_dummies(
    df,
    columns=["city"],
    dtype=int
)

假设 city 中有北京、上海和广州,编码后可能得到:

1
2
3
city_北京
city_上海
city_广州

每一列的值为 01

如果希望少保留一列:

1
2
3
4
5
6
encoded_df = pd.get_dummies(
    df,
    columns=["city"],
    drop_first=True,
    dtype=int
)

实际机器学习项目中,应避免分别对训练集和测试集独立编码,否则两边可能出现不同的列。更完整的项目通常使用 Scikit-learn 的预处理流水线统一处理。


随机抽样和打乱数据

随机抽取 3 行:

1
df.sample(n=3, random_state=42)

随机抽取 20% 的数据:

1
df.sample(frac=0.2, random_state=42)

随机打乱所有行并重新设置索引:

1
2
3
4
df = df.sample(
    frac=1,
    random_state=42
).reset_index(drop=True)

避免链式赋值

下面的写法可能产生警告,并且修改结果不一定符合预期:

1
df[df["score"] < 60]["score"] = 60

推荐使用:

1
df.loc[df["score"] < 60, "score"] = 60

如果需要得到一份独立的筛选结果并继续修改,可以显式复制:

1
2
high_score_df = df[df["score"] >= 85].copy()
high_score_df["level"] = "优秀"

一个完整的数据处理例子

假设原始文件 students.csv 中包含:

1
2
3
4
5
6
name,age,score,city,passed
张三,18,85,北京,1
李四,20,92,上海,1
王五,19,,北京,0
赵六,21,88,广州,1
赵六,21,88,广州,1

完整处理过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import pandas as pd
from sklearn.model_selection import train_test_split

# 1. 读取数据
df = pd.read_csv("students.csv")

# 2. 查看数据基本情况
print(df.head())
print(df.shape)
df.info()
print(df.isna().sum())

# 3. 删除重复行
df = df.drop_duplicates().reset_index(drop=True)

# 4. 转换数据类型
df["age"] = pd.to_numeric(df["age"], errors="coerce")
df["score"] = pd.to_numeric(df["score"], errors="coerce")

# 5. 处理缺失值
df["age"] = df["age"].fillna(df["age"].median())
df["score"] = df["score"].fillna(df["score"].median())
df["city"] = df["city"].fillna("未知")

# 6. 构造新特征
df["score_ratio"] = df["score"] / 100

# 7. 对分类变量进行独热编码
df = pd.get_dummies(
    df,
    columns=["city"],
    dtype=int
)

# 8. 选择特征和标签
x = df.drop(columns=["name", "passed"])
y = df["passed"]

# 9. 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(
    x,
    y,
    test_size=0.2,
    random_state=42
)

print(x_train.shape)
print(x_test.shape)

这个例子完成了:

1
2
3
4
5
6
7
8
9
1. 读取 CSV 文件
2. 查看数据基本信息
3. 删除重复数据
4. 转换数据类型
5. 填充缺失值
6. 构造新特征
7. 编码分类变量
8. 分离特征和标签
9. 划分训练集和测试集

常见注意事项

Pandas 操作通常按照索引对齐

1
2
3
4
s1 = pd.Series([10, 20], index=["a", "b"])
s2 = pd.Series([1, 2], index=["b", "a"])

print(s1 + s2)

结果为:

1
2
3
a    12
b    21
dtype: int64

Pandas 会按照索引标签对齐后再计算,而不是简单地按照位置计算。


count 和 size 的区别

1
df["score"].count()

只统计非空值数量。

1
df["score"].size

统计所有元素数量,包括缺失值。


筛选后索引可能不连续

1
result = df[df["score"] >= 85]

筛选结果会保留原来的索引。

如果需要连续索引:

1
result = result.reset_index(drop=True)

不要默认修改原数据

很多 Pandas 方法会返回一个新对象,而不会直接修改原数据:

1
2
3
df.dropna()
df.sort_values("score")
df.reset_index()

因此通常需要重新赋值:

1
2
3
df = df.dropna()
df = df.sort_values("score")
df = df.reset_index(drop=True)

总结

Pandas 的核心作用是高效处理带有行列标签的表格数据。

在机器学习项目中,Pandas 主要完成以下任务:

1
2
3
4
5
6
7
8
9
10
11
12
1. 从 CSV、Excel 等文件中读取数据
2. 查看数据的形状、类型和统计信息
3. 选择需要的行和列
4. 根据条件筛选样本
5. 处理缺失值和重复值
6. 转换数据类型
7. 构造新的特征
8. 分组统计和合并表格
9. 编码分类变量
10. 分离特征和标签
11. 把数据转换为 NumPy 数组
12. 保存处理后的结果

对于机器学习入门来说,最常用的 Pandas 语法包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pd.Series()
pd.DataFrame()
pd.read_csv()
pd.read_excel()
pd.to_datetime()
pd.to_numeric()
pd.get_dummies()
pd.concat()
pd.merge()

df.head()
df.info()
df.describe()
df.loc[]
df.iloc[]
df.isna().sum()
df.fillna()
df.dropna()
df.drop_duplicates()
df.sort_values()
df.groupby()
df.value_counts()
df.reset_index()
df.to_numpy()
df.to_csv()
本文由作者按照 CC BY 4.0 进行授权