Pandas基本操作
本文对Pandas中的一些基本操作进行描述。
从文件读取数据
有很多 Pandas 的教学,一开始一般都是开始教 Pandas 的数据结构或者运算方法。 但是我觉得,当你想要使用 Pandas 的时候,更多是因为你手头有 Excel 数据或者比较格式化的数据, 需要处理分析和表达这些数据。为了解决你这种当务之急,我觉得先解决读取数据这回事。
以下操作需要用到的文件为['体检数据.xlsx', '体检数据.csv', '体检数据_sep.csv', '体检数据_sep.txt']
Excel文件
点击下载所需文件,下载后用Excel打开文件后是这样:
![](http://cdn.leafii.top/img/Screen Shot 2022-06-05 at 23.31.25.png)
pd.read_excel()
将文件放在适宜的目录,我们就可以使用read_excel()
方法读出文件了。
import pandas as pd
df = pd.read_excel("data/体检数据.xlsx", index_col=0)
df
运行结果
姓名 身高 体重 肺活量
学号
1 小明 168 60 3200
2 小黄 187 80 3800
3 小花 170 70 3400
在函数中的index_col=0
就是告诉Pandas,让它使用第一个column(学号)的数据当作row索引。后面还有很多读取的功能里也有一样的参数。
df.to_excel()
好,我们既然可以读取Excel文件,那么稍稍修改,再保存起来应该也不成问题。
df.loc[2, "体重"] = 1
print(df)
df.to_excel("data/体检数据_修改.xlsx")
其实在读取和保存 Excel 文件的时候,还有很多额外的参数可供选择,因为太多了,我们这里就先讲最常用的,如果你要深入研究, 可以到他们的官网来看官方文档。
csv或txt等纯文本文件
下载需要的文件:链接,使用纯文本编辑器打开,就能看到它最原始的样貌了。
![](http://cdn.leafii.top/img/Screen Shot 2022-06-05 at 23.44.57.png)
pd.read_csv()
使用Python的open打开纯文本文件:
with open("data/体检数据.csv", "r", encoding="utf-8") as f:
print(f.read())
学号,姓名,身高,体重,肺活量
1,小明,168,60,3200
2,小黄,187,80,3800
3,小花,170,70,3400
使用pandas的read_csv()
方法:
df_csv = pd.read_csv("data/体检数据.csv", index_col=0)
df_csv
学号,姓名,身高,体重,肺活量
1,小明,168,60,3200
2,小黄,187,80,3800
3,小花,170,70,3400
有的时候,你不能保证别人给你的数据,是不是一份标准格式的数据,比如别人不喜欢用 ,
来分隔数据点, 而是喜欢用什么乱七八糟的 =
来分隔。这时,Pandas 帮你考虑到了这种问题, 你可以挑选要用哪个字符来识别这些分隔。
with open("data/体检数据_sep.csv", "r", encoding="utf-8") as f:
print(f.read())
df_csv = pd.read_csv("data/体检数据_sep.csv", index_col=0, sep="=")
df_csv
提到 csv,你可能还会想用 Excel 打开看看,但是提到 txt,一般你也不会想用 Excel 打开了吧。用 Pandas 打开一个 txt 文件和打开一个 csv 文件,、 其实本质上是一样的,都是打开一个纯文本文件。所以下面我再打开一下 txt。
with open("data/体检数据_sep.txt", "r", encoding="utf-8") as f:
print(f.read())
df_txt = pd.read_csv("data/体检数据_sep.txt", index_col=0, sep="=")
df_txt
df.to_csv()
能打开,我们就能保存,保存方法同样很简单,只需要 df.to_csv()
就好了,甚至,你还能保存到 Excel 文件,在 Pandas 中它们是可以互相转换的。 同理用 read_excel()
打开的,也能存成 to_csv()
。
df_txt.to_csv("data/体检数据_sep_修改.csv")
df_txt.to_excel("data/体检数据_sep_修改.xlsx")
print("读保存后的 csv")
print(pd.read_csv("data/体检数据_sep_修改.csv"))
print("读保存后的 xlsx")
print(pd.read_excel("data/体检数据_sep_修改.xlsx"))
做数据分析和机器学习,会用上面的方法来读 Excel 或者是纯文本,我们就已经解决了大部分的需求了。下面我来介绍几个我觉得 Pandas 的额外几个有趣的读取方式。
其它有趣的
pd.read_clipboard()
从剪切板里面读取数据,这个很有意思,使用场景并不多,但是指不定哪天你要用 Python 写 APP 的时候,就能用到这个,不是有很多 APP 会识别你的剪切板, 方便你直接复制数据吗。这个功能说不定就能派上用场。
df = pd.read_clipboard()
df
pd.read_html()
另外一个是从网页中获取表格型数据,就像下面这个表格。我们也能用 Pandas 调取解析网页当中的表格数据。
df = pd.read_html("https://mofanpy.com/tutorials/data-manipulation/pandas/read-save/")
df
还有一些功能,比如让 Pandas 读数据库,读 Json 等,你都可能在后期自己开发的时候偶尔用到。只要用到的时候,查查 Pandas 官方文档 还是很有帮助的。
总结
大千世界,数据也是多样的,Pandas 量身为你定制了很多读取数据的方法。从做数据分析和机器学习最常用的 Excel、csv、txt 数据等,到 html,剪切板等有趣的数据类型, 一个 Pandas 都可以帮你搞定。了解完读存数据,接下来,我们就来认真了解,数据在 Pandas 中到底是一个什么样的东西。
Pandas中的数据是什么
上面我们已经提到了在分析数据时最基础的操作就是“用Pandas打开数据文件”,不过我们并没有详细说这份打开的数据它的格式是什么样。但是要了解我们如何更改加工数据,我们必然还是需要了解在Pandas中的数据格式是什么。
简单来说,Pandas支持最好的是一维和二维数组,一维数组就是一个序列,一条数据,而二维数据是我们生活中更加常见的种类,基本上所有Excel数据都是二维数据,有横纵交替,用两个维度来定位这个数据。
下面来说说Pandas中的一维二维数据特性。
数据序列Series
- 创建
一串Python List你肯定不陌生,Pandas中的Series的核心其实就是一串类似于Python List的序列。只是它要比Python List丰富很多,有更多的功能属性。
import pandas as pd
l = [11, 22, 33]
s = pd.Series(l)
print("list:", l)
print("series:", s)
运行结果:
list: [11, 22, 33]
series: 0 11
1 22
2 33
dtype: int64
可见Pandas Series和Python List有很大的区别:Pandas Series帮我们额外维护了一份索引。有这个索引的意义是你可以使用自己喜欢的索引来检索数据:
s = pd.Series(l, index=["a", "b", "c"])
s
运行结果:
a 11
b 22
c 33
dtype: int64
所以,只要是有索引形式的结构,都可以搞成 Series,比如下面这样:
s = pd.Series({"a": 11, "b": 22, "c": 33})
s
运行结果:
a 11
b 22
c 33
dtype: int64
后文中的DataFrame也可以使用字典来创建噢。
- 转换Numpy
既然Python中的List可以用来创建Series,那么使用Numpy也可以创建:
import numpy as np
s = pd.Series(np.random.rand(3), index=["a", "b", "c"])
s
运行结果:
a 0.689052
b 0.284041
c 0.616957
dtype: float64
将Series回退到Numpy array或者List:
print("array:", s.to_numpy())
print("list:", s.values.tolist())
# array: [11 22 33]
# list: [11, 22, 33]
数据表DataFrame
Pandas首先支持的就是序列数据和表格数据,因为这两种是日常生活中最常用的数据保存和编辑格式了,我们看看如何用Pandas的DataFrame维护一张数据表吧。
- 创建
在上一节数据文件读取的教学中,你load到的数据实际上就是一个DataFrame,举个简单的例子,将一个二维数组变成Pandas的DataFrame:
df = pd.DataFrame([
[1,2],
[3,4]
])
df
运行结果:
0 1
0 1 2
1 3 4
显然,它创建出来的df在真实数据外围,还包上了一层其他的数据(0,1),在DataFrame中,这是用来索引行/列的序号,如果要按位置来选去其中的值,也可以这么干。
#第0行,第1列
# 或 第一个维度中的第0号,第二个维度中的第1号
df.at[0, 1]
#2
Pandas 中还有很多方式来选取和改变数据值,请按捺一下激动的小心情, 我们将在下节内容中具体介绍。
自动创建的索引序号并不是很可读。我们还能将这些序号换成人类更好理解的文字标签信息:
df = pd.DataFrame({"col1": [1,3], "col2": [2,4]})
df
运行结果:
col1 col2
0 1 2
1 3 4
可以从结果看出,其实字典中的 key 会被当成是数据中的 column,而 value 会被当做是 row,这个非常符合你在 Excel 中的使用习惯。 因为往往随着数据量变大,你用鼠标滚轮滚动查看不同数据的时候,天然的比较喜欢上下查看不同的数据样本,而不是左右查看,所以一般都是左右记的是数据标签(特征), 上下排列的是不同数据样本。
见识了字典变 DataFrame,其实 Series 也是可以组合变成 DataFrame 的,而且这也非常符合常理, 如果我从 DataFrame 中取出一个 Column, 这不就变成了一条 Series 了吗?
print(df["col1"], "\n")
print("取出来之后的 type:", type(df["col1"]))
运行结果:
0 1
1 3
Name: col1, dtype: int64
取出来之后的 type: <class 'pandas.core.series.Series'>
将两个Series拼接起来:
df = pd.DataFrame({"col1": pd.Series([1,3]), "col2": pd.Series([2,4])})
df
运行结果:
col1 col2
0 1 2
1 3 4
来看看 Series 和 DataFrame 构建索引的方式:
s = pd.Series([1.0, 2.0, 3.0], index=["a", "b", "c"])
df = pd.DataFrame({"col1": [1,3], "col2": [2, 4]}, index=["a", "b"])
print(s, "\n")
print(df)
运行结果:
a 1.0
b 2.0
c 3.0
dtype: float64
col1 col2
a 1 2
b 3 4
既然 DataFrame 的 Column 和 Index 这么有意思,十有八九,你会想取出来用一用这些 Column 和 Index,比如你数据比较大的时候,想初步看看这份数据涉及了多少特征, 数据的 index 有多少种的时候,你可以直接获取到这些信息。
print(df.index, "\n")
print(df.columns)
运行结果:
Index(['a', 'b'], dtype='object')
Index(['col1', 'col2'], dtype='object')
如果写前端的朋友,你们时常会遇到 json 形式的数据,比如可以像下面这样处理。
my_json_data = [
{"age": 12, "height": 111},
{"age": 13, "height": 123}
]
pd.DataFrame(my_json_data, index=["jack", "rose"])
Pandas 真的用心良苦,为我们广大数据同胞提供了这么多这么丰富的接口。学会这样观看数据,我们在分析和处理数据的时候就更有把握了。
总结
Pandas 中,为了我们提供了日常最常用的数据存储方式,分别是 Series 的一维数据,和 DataFrame 的二维数据,在机器学习中,我们常会接触到 3 维甚至是更高维度, 但是在分析数据的时候,特别是,要结合 Excel 来分析数据的时候,二维数据才是最常用的。
选取数据
Pandas的数据结构和你管理Excel很像,特别是DataFrame就约等于Excel当中的sheet。我们非常适应用Excel来选择和修改数据,但是如果把它程序化,用代码来修改和选取的时候,我们该如何操作呢?
Pandas的数据选取和List,Numpy Array还是有挺大差别的,因为它想要维护了很多的人类可读的索引信息,所以它在索引的时候,也有不一样的处理方式,今天我们就来看看Pandas是如何处理数据选取和修改的吧。
多种选取方式
在Pandas中,有丰富的选取数据方式,这可比List,Dictionary,甚至是Numpy还要多样化。我们既能通过文字标签来定位数据,也能通过数值序号来定位。所以为了实现这种多样性,Pandas对于数据的选取采用了不同类型的处理方法,比如.loc
,.iloc
等,我们来一一介绍吧。
面对应用比较多的工作学习场景,我先以 Excel 型的表格数据举例,请你帮我构建一下下面这份 DataFrame:
import numpy as np
import pandas as pd
data = np.arange(-12, 12).reshape((6,4))
df = pd.DataFrame(
data,
index=list("abcdef"),
columns=list("ABCD"))
df
data
运行结果:
A B C D
a -12 -11 -10 -9
b -8 -7 -6 -5
c -4 -3 -2 -1
d 0 1 2 3
e 4 5 6 7
f 8 9 10 11
array([[-12, -11, -10, -9],
[ -8, -7, -6, -5],
[ -4, -3, -2, -1],
[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
选Column
看到上面的这份数据之后,我们发现DataFrame会分Column和Row(index)。在机器学习中,通常Column是特征,Row是数据样本,要对某个特征进行分析的时候,比如要做特征数值分布的分析,我们得把特征取出来吧,那么可以进行如下操作:
df["B"]
运行结果:
a -11
b -7
c -3
d 1
e 5
f 9
Name: B, dtype: int64
选一个就这么简单,但偶尔还想多选几个特征,怎么搞呢?回忆一下之前Numpy中的Array是怎么选的?
print("numpy:\n", data[:,[2,1]])
print("\ndf:\n", df[["C","B"]])
运行结果:
numpy:
[[-10 -11]
[ -6 -7]
[ -2 -3]
[ 2 1]
[ 6 5]
[ 10 9]]
df:
C B
a -10 -11
b -6 -7
c -2 -3
d 2 1
e 6 5
f 10 9
现在可以选column了,那意味着肯定能将Index(Row)的信息也一起考虑到数据筛选的工作当中了。
loc
在Numpy中选取数据一般是按照在维度上的排序来定位的。比如对于你刚刚创建的Numpy数据data
:
data[2:3,1:3]
运行结果:
array([[-3, -2]])
而在DataFrame中,同样是上述功能,你也可以这么干:
df.loc["c":"d", "B":"D"]
运行结果:
B C D
c -3 -2 -1
d 1 2 3
不知道你有没有发现,这个 "c":"d"
和 "B":"D"
明明对应的是上面 data
的 [2:3]
和 [1:3]
,但为什么它还包含了最后一位的 "d"
和 "D"
呢?这的确是 Pandas 的一个用心良苦,我猜他是为了更贴切 Excel 中的使用原则吧,想一想,如果你像下面这样, 在选择 Excel 要被筛选的数据时,从 b 选到 d,其实你是有包含 d 的。所以我说,Pandas 这么设计,原因之一也应该是为了照顾我们吧。
除了筛选一个片段,还可以像 Numpy 那样单个单个的选取。
print("numpy:\n",data[[3,1],:])
print("\ndf:\n",df.loc[["d","b"],:])
运行结果:
numpy:
[[ 0 1 2 3]
[-8 -7 -6 -5]]
df:
A B C D
d 0 1 2 3
b -8 -7 -6 -5
上面这两个例子,不难看出,Pandas 选取数据的底层逻辑,和 Python Numpy 的类似,都是按维度的先后(先选第一维,再第二,以此类推), 开始选取。按数据的索引找到维度上的对应索引区域。
下面我再来整个有趣的,如果我不按字母顺序去组织 index,比如从原本的 index=abcdef
换成 index=beacdf
, 猜猜下面的这份数据索引会找到哪一份子数据?
df2 = pd.DataFrame(
data,
index=list("beacdf"),
columns=list("ABCD"))
print(df2)
print(df2.loc["e":"c"])
运行结果:
A B C D
b -12 -11 -10 -9
e -8 -7 -6 -5
a -4 -3 -2 -1
c 0 1 2 3
d 4 5 6 7
f 8 9 10 11
A B C D
e -8 -7 -6 -5
a -4 -3 -2 -1
c 0 1 2 3
iloc
看完面向 Excel 编程,我们再来回到编程本身,用程序的思维去选取数据。这意味着什么?用最朴素的方法,也是意味着能更快找到数据位置,比如直接用位置信息来筛选。 Numpy 不就是这么干的吗?这时 .iloc
功能就派上用场了。
看看iloc
的功能是什么:
print("numpy:\n", data[2:3, 1:3])
print("\ndf:\n", df.iloc[2:3, 1:3])
运行结果:
numpy:
[[-3 -2]]
df:
B C
c -3 -2
简直和 Numpy 的模式一模一样,就是结果中多了一个 DataFrame 的标签信息。
print("numpy:\n",data[[3,1],:])
print("\ndf:\n",df.iloc[[3, 1],:])
运行结果:
numpy:
[[ 0 1 2 3]
[-8 -7 -6 -5]]
df:
A B C D
d 0 1 2 3
b -8 -7 -6 -5
loc和iloc混搭
难免有时候,我们需要混搭 loc 和 iloc 的方式,比如我想要选取第 2 到第 4 位数据的 A C 两个特征,这时咋办? 想想 Pandas 这么牛逼,肯定有办法解决。的确,它解决的方法是采用索引转换的方式,比如我在 .loc
模式下,将序号索引转换成 .loc
的标签索引。
row_labels = df.index[2:4]
print("row_labels:\n", row_labels)
print("\ndf:\n",df.loc[row_labels,["A","C"]])
运行结果:
row_labels:
Index(['c', 'd'], dtype='object')
df:
A C
c -4 -2
d 0 2
再看看 Column 的 labels 怎么取:
col_labels = df.columns[[0, 3]]
print("col_labels:\n", col_labels)
print("\ndf:\n", df.loc[row_labels, col_labels])
运行结果:
col_labels:
Index(['A', 'D'], dtype='object')
df:
A D
c -4 -1
d 0 3
清楚了吧,用 df.index
和 df.columns
来调取到全部的标签,然后在用像 Numpy index 索引的方式把这些标签给筛选出来,放到 .loc
里面用。 那反过来,我想要找 A C 两个特征的 前两个数据,这时咋办?
col_index = df.columns.get_indexer(["A", "B"])
print("col_index:\n", col_index)
print("\ndf:\n", df.iloc[:2, col_index])
运行结果:
col_index:
[0 1]
df:
A B
a -12 -11
b -8 -7
同理,df.index.get_indexer(["a", "b"])
也可以这样获取到 label 对应的 index 信息。
条件过滤筛选
按条件过滤其实是一件很有趣的事,因为很多情况我们事先也不知道具体的 index 是什么,我们更想要从某些条件中筛选数据。 下面我举几个例子,大家应该很容易 get 到其中的奥秘。
- 选在 A Column 中小于 0 的那些数据
df[df["A"] < 0]
运行结果:
A B C D
a -12 -11 -10 -9
b -8 -7 -6 -5
c -4 -3 -2 -1
- 选在第一行数据不小于 -10 的数据
这里注意了你可以用两种方式,一种是 ~
来表示 非
什么什么,第二种是直接用 >=-10
来筛选。
print("~:\n", df.loc[:, ~(df.iloc[0] < -10)])
print("\n>=:\n", df.loc[:, df.iloc[0] >= -10])
运行结果:
~:
C D
a -10 -9
b -6 -5
c -2 -1
d 2 3
e 6 7
f 10 11
>=:
C D
a -10 -9
b -6 -5
c -2 -1
d 2 3
e 6 7
f 10 11
- 选在第一行数据不小于 -10 或小于 -11 的数据
同上面类似的,我还能用或 |
来表示 or 的意思, &
表述 and。比如选在第一行数据不小于 -10 或小于 -11 的数据:
i0 = df.iloc[0]
df.loc[:, ~(i0 < -10) | (i0 < -11)]
运行结果:
A C D
a -12 -10 -9
b -8 -6 -5
c -4 -2 -1
d 0 2 3
e 4 6 7
f 8 10 11
所以你看,你可以用 .loc
或者 .iloc
来做过滤处理。然后用 .loc
来做筛选。为什么用 .iloc
来筛选呢,比如下面这样:
df.iloc[:, ~(df.iloc[0] < -10)]
#报错
这样写居然会导致报错,为什么呢?你分析分析,把 ~(df.iloc[0] < -10)
这个筛选条件打印出来看看它是什么值。然后想一想 .iloc
能够接受的值是啥? 想清楚了,这个问题就迎刃而解了。
A False
B False
C True
D True
Name: a, dtype: bool
Series和DataFrame类似
既然二维的 DataFrame 你都已经玩过了,Series 的操作就不在话下了。
list_data = list(range(-4, 4))
s = pd.Series(
list_data,
index=list("abcdefgh"))
s
运行结果:
a -4
b -3
c -2
d -1
e 0
f 1
g 2
h 3
dtype: int64
- 按标签筛选数据
.loc
print(s.loc[["a", "g", "c"]], "\n")
print(s.loc["c": "f"])
运行结果:
a -4
g 2
c -2
dtype: int64
c -2
d -1
e 0
f 1
dtype: int64
- 按 index 筛选数据
.iloc
print(s.iloc[[3, 1, 5]], "\n")
print(s.iloc[2: 4])
运行结果:
d -1
b -3
f 1
dtype: int64
c -2
d -1
dtype: int64
iloc
和loc
互相混用
print(s.iloc[s.index.get_indexer(["c", "d"])], "\n")
print(s.loc[s.index[[3,2]]])
运行结果:
c -2
d -1
dtype: int64
d -1
c -2
dtype: int64
- 按条件过滤筛选
print(s.loc[s < 3], "\n")
print(s.loc[(s < 0) & (s > -2)], "\n")
print(s.loc[(s < 0) | (s > 2)], "\n")
运行结果:
a -4
b -3
c -2
d -1
e 0
f 1
g 2
dtype: int64
d -1
dtype: int64
a -4
b -3
c -2
d -1
h 3
dtype: int64
总结
Pandas 的数据筛选方法比 Numpy 丰富多了,介绍的篇幅也有点多,我日常用的最多的还是用条件来筛选,比如在处理机器学习的脏数据的时候,要用很多筛选逻辑。 有时甚至我会觉得 Numpy 的筛选方式不够多,专门把 Numpy 数据转换成 Pandas 数据,然后再用 Pandas 提供的丰富工具处理数据,再转回 Numpy。 相信你用熟了之后,数据处理将要简单很多。