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.indexdf.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
  • ilocloc 互相混用
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。 相信你用熟了之后,数据处理将要简单很多。