原文链接:https://wklchris.github.io/Py3-pandas.html
pandas 是 Python 下科学计算非常实用的一个工具。本文将简明扼要地介绍其使用方法。如果想要初步地操作数据,直接阅读本文即可;否则请先学习 numpy 相关的内容。
引言:Series 与 DataFrame
加载 pandas 时,一般也会同时加载 numpy. 默认使用如下的语句:
import numpy as np
import pandas as pd
from pandas import Series, DataFrame
pandas 内置了两种独特的数据结构:Series 与 DataFrame.
系列(Series)
系列是由多个同类型元素组成的有序列向量,有些接近 Python 原生的列表(list)。不同的是,系列包含各元素的一个索引:这个索引可以任意重命名。
s = Series([1, 2, np.nan, 4])
s
0 1.0
1 2.0
2 NaN
3 4.0
dtype: float64
s.index = list('abcd')
s
a 1.0
b 2.0
c NaN
d 4.0
dtype: float64
s["c"]
nan
数据框(DataFrame)
数据框是一个二维的数据结构,特点是:
列内的元素同类型,不同的列之间可以不相同。
索引有两个轴向:axis=0/”index”行,axis=1/”columns” 列。分别用 df.index(行名)与df.columns(列名)调用。
在数据处理中,使用数据框是非常便捷的;而系列我们却很少使用。
数据读写
数据读取
pandas 内置的文件读取函数:
pd.read_csv():参数如下。
names:指定表头(df.columns)。
header:指定整数 n,表示前 n 行会被读作表头。如果 names 指定了,会被设为 0.
engine: “c” 或者 “python”。前者会快一些。
skipinitialspace: 是否忽略紧跟在分隔符后的空格。默认值 False.
na_values:指定一个字符串列表,里面的字符串都会被识别为 NaN。默认的有 [‘’, ‘#N/A’, ‘#N/A N/A’, ‘#NA’, ‘-1.#IND’, ‘-1.#QNAN’, ‘-NaN’, ‘-nan’,‘1.#IND’, ‘1.#QNAN’, ‘N/A’, ‘NA’, ‘NULL’, ‘NaN’, ‘nan’.]
na_filter:默认值 True。如果确定文件中不含 NaN,那么指定它为 False 可以提高大文件读取速度。 -skip_blank_lines:是否跳过空白行。默认值 True.
pd.read_table():只要是分隔符文件即可。参数 sep 缺省值是制表符。
pd.read_excel()
以上读取的结果都是 DataFrame. 其他的读取函数不再介绍。
数据写入
df.to_csv()
df.to_excel()
数据创建
以下介绍几种数据框的创建方法。第一种,如果是全数字的数据结构,可以直接从 numpy 中的矩阵创建。
# 参数 index 与 columns 是可选的
arr = np.arange(1, 19).reshape([6, 3])
df = DataFrame(arr, index=list('ABCDEF'), columns=list('ZYX'))
df
Z | Y | X | |
---|---|---|---|
A | 1 | 2 | 3 |
B | 4 | 5 | 6 |
C | 7 | 8 | 9 |
D | 10 | 11 | 12 |
E | 13 | 14 | 15 |
F | 16 | 17 | 18 |
第二种,通过字典创建。字典的每个键下的值都是格式相同的列表。注意:此时的列会自动根据列名(原字典中的键)从左到右升序排列。
df = DataFrame({"Letters": ["A", "B"], "Age": [12, 34]})
df
Age | Letters | |
---|---|---|
0 | 12 | A |
1 | 34 | B |
数据框尺寸:df.shape 与 len()
与 numpy 的矩阵类似,数据框拥有属性 shape,返回结果是一个元组:
df = DataFrame(arr, index=list('ABCDEF'), columns=list('ZYX'))
df.shape
(6, 3)
如果使用 Python 原生的 len() 函数,会返回数据框的行数。注意:如果你要基于数据框写循环语句,请利用它们的行名(df.index)而不是利用 len() 函数。这是因为数据框在切片时会保留行名索引;如果简单地使用 for row in range(len(...))
,会出现 row 与行名不一致的问题。
len(df)
6
预览数据:df.head() / tail() / values
常用的是前两个,用于查看数据框头部 5 行或者尾部 5 行的数据。你也可以传入数字指定查看的行数。
df.head(3)
Z | Y | X | |
---|---|---|---|
A | 1 | 2 | 3 |
B | 4 | 5 | 6 |
C | 7 | 8 | 9 |
df.tail()
Z | Y | X | |
---|---|---|---|
B | 4 | 5 | 6 |
C | 7 | 8 | 9 |
D | 10 | 11 | 12 |
E | 13 | 14 | 15 |
F | 16 | 17 | 18 |
df.values 则是一个有些怪异的命令:它会返回一个 np.array 类型的数据。
df.values
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12],
[13, 14, 15],
[16, 17, 18]])
查看 df 的基础信息,使用 df.info() 命令:
df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 6 entries, A to F
Data columns (total 3 columns):
Z 6 non-null int32
Y 6 non-null int32
X 6 non-null int32
dtypes: int32(3)
memory usage: 96.0+ bytes
统计信息:df.describe(), s.value_counts() / unique()
df.describe()
Z | Y | X | |
---|---|---|---|
count | 6.000000 | 6.000000 | 6.000000 |
mean | 8.500000 | 9.500000 | 10.500000 |
std | 5.612486 | 5.612486 | 5.612486 |
min | 1.000000 | 2.000000 | 3.000000 |
25% | 4.750000 | 5.750000 | 6.750000 |
50% | 8.500000 | 9.500000 | 10.500000 |
75% | 12.250000 | 13.250000 | 14.250000 |
max | 16.000000 | 17.000000 | 18.000000 |
value_counts() 是一个适用于 Series 的命令。参数 dropna 可以使用。
df.apply(Series.value_counts, dropna=False)
Z | Y | X | |
---|---|---|---|
1 | 1.0 | NaN | NaN |
2 | NaN | 1.0 | NaN |
3 | NaN | NaN | 1.0 |
4 | 1.0 | NaN | NaN |
5 | NaN | 1.0 | NaN |
6 | NaN | NaN | 1.0 |
7 | 1.0 | NaN | NaN |
8 | NaN | 1.0 | NaN |
9 | NaN | NaN | 1.0 |
10 | 1.0 | NaN | NaN |
11 | NaN | 1.0 | NaN |
12 | NaN | NaN | 1.0 |
13 | 1.0 | NaN | NaN |
14 | NaN | 1.0 | NaN |
15 | NaN | NaN | 1.0 |
16 | 1.0 | NaN | NaN |
17 | NaN | 1.0 | NaN |
18 | NaN | NaN | 1.0 |
unique() 命令有时会配合集合(set)数据结构一同使用:
tmp = Series([1, 2, 3, np.nan, 14, 3, 1])
set_1 = set(tmp.unique())
set_1
{nan, 1.0, 2.0, 3.0, 14.0}
更改列类型:pd.to_numeric()
pandas 在读入数据时会自动识别各列的类型。识别的类型可以使用 dtypes 属性:
df.dtypes
Z int32
Y int32
X int32
dtype: object
例如,我们将前两列的属性改为字符串:(这里使用的 applymap 函数会在后面提到)
df[["Z", "Y"]] = df[["Z", "Y"]].applymap(str)
df.dtypes
Z object
Y object
X int32
dtype: object
# Check it
df["Z"][1]
'4'
对于上述的问题,astype() 命令也是一个解决方案:
df[["Z", "Y"]] = df[["Z", "Y"]].astype(str)
如果想转换为数字,pandas 提供了 to_numeric 命令。此命令是作用于 Series 的,因此你可能需要 apply 命令来应用于多个列:
df[["Z", "Y"]] = df[["Z", "Y"]].apply(pd.to_numeric)
df.dtypes
Z int64
Y int64
X int32
dtype: object
注意到该命令有个 errors 参数,用于处理无法正常转换的情况。可以指定以下值:
“ignore”: 如果列中存在无法转换的元素,那么整个列不作转换;
“coerce”:如果列中存在无法转换的元素,仍然转换列,并将这些元素转换为 NaN
tmp = Series([1, 2, "Tree", 4])
pd.to_numeric(tmp, errors="ignore")
0 1
1 2
2 Tree
3 4
dtype: object
pd.to_numeric(tmp, errors="coerce")
0 1.0
1 2.0
2 NaN
3 4.0
dtype: float64
更改行 / 列名:df.rename()
数据框的行名 / 列名列表分别用 df.columns 与 df.index 查看。
df.columns
Index(['Z', 'Y', 'X'], dtype='object')
df.index
Index(['A', 'B', 'C', 'D', 'E', 'F'], dtype='object')
因此,最简单粗暴的行名 / 列名更改方法是:将一个列表赋值过去。
df.columns = list("RQP")
df.head(1)
R | Q | P | |
---|---|---|---|
A | 1 | 2 | 3 |
有时需要只更改几个名称,难道需要创建整个列表吗?这时候使用 df.rename 命令,通过字典的方式替换:
# 在很多函数中,inplace 参数都避免了自赋值的操作
df.rename(columns = {"R": "Z", "Q": "Y", "P": "X"}, inplace=True)
df.head(1)
Z | Y | X | |
---|---|---|---|
A | 1 | 2 | 3 |
数据整理
深度复制:df.copy()
不用多说。如果不进行深度复制,df 与 df2 将会是同址的。
df2 = df.copy()
关于切片时的深度复制问题,会在介绍切片时阐述。
转置:df.T
df.T # 不改变 df
A | B | C | D | E | F | |
---|---|---|---|---|---|---|
Z | 1 | 4 | 7 | 10 | 13 | 16 |
Y | 2 | 5 | 8 | 11 | 14 | 17 |
X | 3 | 6 | 9 | 12 | 15 | 18 |
排序
排序分为两种:
按照列名/行名的字母顺序排序列/行,这里称为索引重排;
按照列内/行内的数据间大小关系,排序整个数据框的行/列,这里称为(数据)排序。
索引重排:df.sort_index()
默认参数是 axis=0
(重排行),ascending=True
(升序)。支持 inplace 参数。
df.sort_index(ascending=False)
Z | Y | X | |
---|---|---|---|
F | 16 | 17 | 18 |
E | 13 | 14 | 15 |
D | 10 | 11 | 12 |
C | 7 | 8 | 9 |
B | 4 | 5 | 6 |
A | 1 | 2 | 3 |
df.sort_index(axis="columns") # 或 axis = 1
X | Y | Z | |
---|---|---|---|
A | 3 | 2 | 1 |
B | 6 | 5 | 4 |
C | 9 | 8 | 7 |
D | 12 | 11 | 10 |
E | 15 | 14 | 13 |
F | 18 | 17 | 16 |
数据排序:df.sort_values()
此时只能根据列来排序。参数 na_position
默认值是 “last”,也可以指定 “first”.
df.sort_values(by="Z", ascending=False)
Z | Y | X | |
---|---|---|---|
F | 16 | 17 | 18 |
E | 13 | 14 | 15 |
D | 10 | 11 | 12 |
C | 7 | 8 | 9 |
B | 4 | 5 | 6 |
A | 1 | 2 | 3 |
切片与选取
普通选取
选取单列:
df['X']
或者df.X
选取多列:
df[['X', 'Y']]
选取行:例如前两行
df[:2]
按照标签:
df.loc['A']
/df.loc[['A', 'E'], 'X']
按照索引序号:第三行
df.iloc[2]
;df.iloc[:2, :1]
单元素选取:
df.at
/df.iat
,只能选取单个元素。
在多数情况下,请尽量使用 loc/iloc 命令。它们是深度复制命令,这样可以避免多数的引用问题。
布尔型选取
df[(df.X > 10) & (df.Y < 15)]
Z | Y | X | |
---|---|---|---|
D | 10 | 11 | 12 |
E | 13 | 14 | 15 |
df[df > 10] # 对整个 df 进行判断,不符合的填充 NaN
Z | Y | X | |
---|---|---|---|
A | NaN | NaN | NaN |
B | NaN | NaN | NaN |
C | NaN | NaN | NaN |
D | NaN | 11.0 | 12.0 |
E | 13.0 | 14.0 | 15.0 |
F | 16.0 | 17.0 | 18.0 |
df[df.X.isin([3, 6, 8])] # 根据列 X 中是否有列表里对应元素,筛选行
Z | Y | X | |
---|---|---|---|
A | 1 | 2 | 3 |
B | 4 | 5 | 6 |
缺失数据:df.dropna() / fillna()
df.dropna() 将筛选含有 NaN 的行/列(用参数 axis 指定),支持 inplace 参数。参数 how 指定筛选的方式:
df.dropna(how=”any”):行/列中存在 NaN 就抛弃。
df.dropna(how=”all”):只有当行/列全为 NaN 时才抛弃。
参数 thresh 指定了行/列中最低应存在的非 NaN 元素个数;如果非 NaN 元素个数小于这一阈值,该行/列会被抛弃。
df.fillna() 将 NaN 替换为另外的数值,用参数 value 指定。例如:df.fillna(value=-1)
。
pd.isnull() 命令将会返回一个布尔型数据框,检验各元素是否为 NaN。
pd.isnull(df).head(2)
Z | Y | X | |
---|---|---|---|
A | False | False | False |
B | False | False | False |
查找替换:df.replace()
df.replace([1, 2], [100, 200])
Z | Y | X | |
---|---|---|---|
A | 100 | 200 | 3 |
B | 4 | 5 | 6 |
C | 7 | 8 | 9 |
D | 10 | 11 | 12 |
E | 13 | 14 | 15 |
F | 16 | 17 | 18 |
合并与分组
合并:df.append() / join(), pd.concat()
append() 是源自 list 类型的函数,类似 list 类型的 DataFrame 也可以调用。其作用是行合并。
df1 = df
df2 = df.rename(columns={"X": "Z", "Z": "P"})
df1.append(df2)
P | X | Y | Z | |
---|---|---|---|---|
A | NaN | 3.0 | 2 | 1 |
B | NaN | 6.0 | 5 | 4 |
C | NaN | 9.0 | 8 | 7 |
D | NaN | 12.0 | 11 | 10 |
E | NaN | 15.0 | 14 | 13 |
F | NaN | 18.0 | 17 | 16 |
A | 1.0 | NaN | 2 | 3 |
B | 4.0 | NaN | 5 | 6 |
C | 7.0 | NaN | 8 | 9 |
D | 10.0 | NaN | 11 | 12 |
E | 13.0 | NaN | 14 | 15 |
F | 16.0 | NaN | 17 | 18 |
pd.concat() 则是一个可以指定合并轴的函数:
pd.concat([df1, df2], axis="columns") # 列合并
Z | Y | X | P | Y | Z | |
---|---|---|---|---|---|---|
A | 1 | 2 | 3 | 1 | 2 | 3 |
B | 4 | 5 | 6 | 4 | 5 | 6 |
C | 7 | 8 | 9 | 7 | 8 | 9 |
D | 10 | 11 | 12 | 10 | 11 | 12 |
E | 13 | 14 | 15 | 13 | 14 | 15 |
F | 16 | 17 | 18 | 16 | 17 | 18 |
如果按默认的合并方式 join=”outer”,那么 concat(axis="index")
的结果与 append() 一致。但你可以指定内联合并:
pd.concat([df1, df2], axis="index", join="inner")
Y | Z | |
---|---|---|
A | 2 | 1 |
B | 5 | 4 |
C | 8 | 7 |
D | 11 | 10 |
E | 14 | 13 |
F | 17 | 16 |
A | 2 | 3 |
B | 5 | 6 |
C | 8 | 9 |
D | 11 | 12 |
E | 14 | 15 |
F | 17 | 18 |
df.join() 则允许你使用类似 SQL 的方法进行合并。利用其参数 how 与 on 等可以控制合并方式。
分组:df.groupby()
df.groupby() 函数与 R 语言中的 aggregate() 有异曲同工之妙。
dt = DataFrame({"A": [1, 2, 3, 4, 5, 6],
"B": ["a", "a", "a", "b", "b", "b"],
"C": ["x", "x", "y", "y", "z", "z"],
"D": [1, 1, 1, 2, 2, 2]})
dt.groupby("B")
<pandas.core.groupby.DataFrameGroupBy object at 0x063332D0>
返回值无法直接理解,需要配合统计函数使用。可以参考本文“统计函数”一节的内容。
dt.groupby("B").mean() # 列 C 无法计算 mean(),被忽略
A | D | |
---|---|---|
B | ||
a | 2 | 1 |
b | 5 | 2 |
也可以同时应用到多个列。如果你了解过 R 语言中“因子”的概念,会对该函数的理解有所帮助。列 B 有两个水平,列 C 有三个。
dt.groupby(["B", "C"]).mean()
A | D | ||
---|---|---|---|
B | C | ||
a | x | 1.5 | 1.0 |
y | 3.0 | 1.0 | |
b | y | 4.0 | 2.0 |
z | 5.5 | 2.0 |
groupby() 函数默认会将函数应用到所有其他的列。如果你只想计算指定的列,那么:
dt.groupby(["B", "C"])["A"].mean()
B C
a x 1.5
y 3.0
b y 4.0
z 5.5
Name: A, dtype: float64
函数式应用:df.apply()/applymap()
df.apply() 函数在前面已经介绍过。参数主要是 axis,以及也能继承要应用的函数的参数。所有可以应用于 Series 的函数都能够这样应用到不同的列上。这里再次使用上文使用过的 Series.value_counts() 函数。
df.apply(Series.value_counts, dropna=False).head(4)
Z | Y | X | |
---|---|---|---|
1 | 1.0 | NaN | NaN |
2 | NaN | 1.0 | NaN |
3 | NaN | NaN | 1.0 |
4 | 1.0 | NaN | NaN |
一个用 df.applymap() 实现的小数位数处理,保留到第二位:
df.applymap(lambda x: "%.2f" % x)
Z | Y | X | |
---|---|---|---|
A | 1.00 | 2.00 | 3.00 |
B | 4.00 | 5.00 | 6.00 |
C | 7.00 | 8.00 | 9.00 |
D | 10.00 | 11.00 | 12.00 |
E | 13.00 | 14.00 | 15.00 |
F | 16.00 | 17.00 | 18.00 |
统计函数
除了上文介绍过的 df.describe(),以下运算均以“列”为基本单位:
df.mean()
df.corr()
df.count():非 NaN 数据计数
df.max()/df.minx()
df.median()
df.std()
猜你可能喜欢