Skip to content

3.Pandas概述

Pandas概述

Pandas(Python Data Analysis Library )是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具。Pandas提供了大量能使我们快速便捷地处理数据的函数和方法。你很快就会发现,它是使Python成为强大而高效的数据分析环境的重要因素之一。

Pandas是Python的一个数据分析包,最初由AQR Capital Management于2008年4月开发,并于2009年底开源出来,目前由专注于Python数据包开发的PyData开发team继续开发和维护,属于PyData项目的一部分。Pandas最初被作为金融数据分析工具而开发出来。

Pandas含有使数据分析工作变得更快更简单的高级数据结构和操作工具。pandas是基于Numpy构建的,让以Numpy为中心的应用变得更简单。

Pandas专用于数据预处理和数据分析的Python第三方库,最适合处理大型结构化表格数据

  • Pandas是2008年Wes McKinney于AQR资本做量化分析师时创建
  • Pandas借鉴了R的数据结构
  • Pandas基于Numpy搭建,支持Numpy中定义的大部分计算
  • Pandas含有使数据分析工作更简单高效的高级数据结构和操作工具
  • Pandas底层用Cython和C做了速度优化,极大提高了执行效率

Pandas 有很多高级的功能,但是想要掌握高级功能前,需要先掌握它的基础知识,Pandas 中的数据结构算是非常基础的知识之一了。

Pandas 常用的数据结构有两种:Series 和 DataFrame。这些数据结构构建在 Numpy 数组之上,这意味着它们效率很高。我们来分别看看这些数据结构都长什么样子吧。

# 导入相关库
import numpy as np
import pandas as pd

Series

简介

Series 是一个带有 名称 和索引的一维数组,既然是数组,肯定要说到的就是数组中的元素类型,在 Series 中包含的数据类型可以是整数、浮点、字符串、Python对象等。

假定有一个场景是:存储一些用户的信息,暂时只包括年龄信息。

我们可以通过 Series 来存储,这里我们通过 Series 存储了四个年龄:18/30/25/40,只需将要存储的数据构建成一个数组,然后赋值给data参数即可

# 存储了 4 个年龄:18/30/25/40
user_age = pd.Series(data=[18, 30, 25, 40])
user_age
0    18
1    30
2    25
3    40
dtype: int64

可以看到,已经正确将多个年龄存储到 Series 中了,你可能会想,单独存储了年龄有什么用,我怎么知道这个年龄属于哪个用户呢?

我们可以通过 Series 的 index(索引)来解决这个问题。由于有四个年龄,自然地也需要四个姓名,所以我们需要构建一个与 data 长度相同的数组,然后通过下面的操作即可满足要求。

user_age.index = ["Tom", "Bob", "Mary", "James"]
user_age
Tom      18
Bob      30
Mary     25
James    40
dtype: int64

你看,现在姓名与年龄已经完全对应上了。虽然说我们自己知道 Tom/Bob 这些是姓名,但是别人不知道啊,我们怎么告诉他人呢?

要想让别人知道,我们可以为 index 起个名字。

user_age.index.name = "name"
user_age
name
Tom      18
Bob      30
Mary     25
James    40
dtype: int64

可能你还会想,如果别人在看我写的代码,怎么能快速的知道我这写的到底是什么玩意呢?

别急,就像我们给index起名字一样,我们也可以给 Series 起个名字。

user_age.name="user_age_info"
user_age
name
Tom      18
Bob      30
Mary     25
James    40
Name: user_age_info, dtype: int64

通过上面一系列的操作,我们对 Series 的结构上有了基本的了解,简单来说,一个 Series 包括了 data、index 以及 name

上面的操作非常方便做演示来使用,如果想要快速实现上面的功能,可以通过以下方式来实现。

# 构建索引
name = pd.Index(["Tom", "Bob", "Mary", "James"], name="name")
# 构建 Series
user_age = pd.Series(data=[18, 30, 25, 40], index=name, name="user_age_info")
user_age
name
Tom      18
Bob      30
Mary     25
James    40
Name: user_age_info, dtype: int64

另外,需要说明的是我们在构造 Series 的时候,并没有设定每个元素的数据类型,这个时候,Pandas 会自动判断一个数据类型,并作为 Series 的类型。

当然了,我们也可以自己手动指定数据类型。

# 指定类型为浮点型
user_age = pd.Series(data=[18, 30, 25, 40], index=name, name="user_age_info", dtype=float)
user_age
name
Tom      18.0
Bob      30.0
Mary     25.0
James    40.0
Name: user_age_info, dtype: float64

Series 像什么

Series 包含了 dict 的特点,也就意味着可以使用与 dict 类似的一些操作。我们可以将 index 中的元素看成是 dict 中的 key。

# 获取 Tom 的年龄
user_age["Tom"]
18.0

此外,可以通过 get 方法来获取。通过这种方式的好处是当索引不存在时,不会抛出异常。

user_age.get("Tom")
18.0

Series 除了像 dict 外,也非常像 ndarray,这也就意味着可以采用切片操作。

# 获取第一个元素
user_age[0]
18.0
# 获取前三个元素
user_age[:3]
name
Tom     18.0
Bob     30.0
Mary    25.0
Name: user_age_info, dtype: float64
# 获取年龄大于30的元素
user_age[user_age > 30]
name
James    40.0
Name: user_age_info, dtype: float64
# 获取第4个和第二个元素
user_age[[3, 1]]
name
James    40.0
Bob      30.0
Name: user_age_info, dtype: float64

可以看到,无论我们通过切片如何操作 Series ,它都能够自动对齐 index。

Series 的向量化操作

Series 与 ndarray 一样,也是支持向量化操作的。同时也可以传递给大多数期望 ndarray 的 NumPy 方法。

user_age + 1
name
Tom      19.0
Bob      31.0
Mary     26.0
James    41.0
Name: user_age_info, dtype: float64
np.exp(user_age)
name
Tom      6.565997e+07
Bob      1.068647e+13
Mary     7.200490e+10
James    2.353853e+17
Name: user_age_info, dtype: float64

DataFrame

DataFrame 是一个带有索引的二维数据结构,每列可以有自己的名字,并且可以有不同的数据类型。你可以把它想象成一个 excel 表格或者数据库中的一张表,DataFrame 是最常用的 Pandas 对象。

我们继续使用之前的实例来讲解 DataFrame,在存储用户信息时,除了年龄之外,我还想存储用户所在的城市。如何通过 DataFrame 实现呢?

可以构建一个 dict,key 是需要存储的信息,value 是信息列表。然后将 dict 传递给 data 参数

index = pd.Index(data=["Tom", "Bob", "Mary", "James"], name="name")

data = {
    "age": [18, 30, 25, 40],
    "city": ["BeiJing", "ShangHai", "GuangZhou", "ShenZhen"]
}

user_info = pd.DataFrame(data=data, index=index)
user_info
agecity
name
Tom18BeiJing
Bob30ShangHai
Mary25GuangZhou
James40ShenZhen

可以看到,我们成功构建了一个 DataFrame,这个 DataFrame 的索引是用户性别,还有两列分别是用户的年龄和城市信息。

除了上面这种传入 dict 的方式构建外,我们还可以通过另外一种方式来构建。这种方式是先构建一个二维数组,然后再生成一个列名称列表

data = [[18, "BeiJing"], 
        [30, "ShangHai"], 
        [25, "GuangZhou"], 
        [40, "ShenZhen"]]
columns = ["age", "city"]

user_info = pd.DataFrame(data=data, index=index, columns=columns)
user_info
agecity
name
Tom18BeiJing
Bob30ShangHai
Mary25GuangZhou
James40ShenZhen

访问行

在生成了 DataFrame 之后,可以看到,每一行就表示某一个用户的信息,假如我想要访问 Tom 的信息,我该如何操作呢?

一种办法是通过索引名来访问某行,这种办法需要借助 loc 方法。

user_info.loc["Tom"]
age          18
city    BeiJing
Name: Tom, dtype: object

除了直接通过索引名来访问某一行数据之外,还可以通过这行所在的位置来选择这一行。

user_info.iloc[0]
age          18
city    BeiJing
Name: Tom, dtype: object

现在能够访问某一个用户的信息了,那么我如何访问多个用户的信息呢?也就是如何访问多行呢?

借助行切片可以轻松完成,来看这里。

user_info.iloc[1:3]
agecity
name
Bob30ShangHai
Mary25GuangZhou

访问列

学会了如何访问行数据之外,自然而然会想到如何访问列。我们可以通过属性(“.列名”)的方式来访问该列的数据,也可以通过[column]的形式来访问该列的数据。

假如我想获取所有用户的年龄,那么可以这样操作。

user_info.age
name
Tom      18
Bob      30
Mary     25
James    40
Name: age, dtype: int64
user_info["age"]
name
Tom      18
Bob      30
Mary     25
James    40
Name: age, dtype: int64

如果想要同时获取年龄和城市该如何操作呢?

# 可以变换列的顺序
user_info[["city", "age"]]
cityage
name
TomBeiJing18
BobShangHai30
MaryGuangZhou25
JamesShenZhen40

新增/删除列

在生成了 DataFrame 之后,突然你发现好像缺失了用户的性别这个信息,那么如何添加呢?

如果所有的性别都一样,我们可以通过传入一个标量,Pandas 会自动帮我们广播来填充所有的位置。

user_info["sex"] = "male"
user_info
agecitysex
name
Tom18BeiJingmale
Bob30ShangHaimale
Mary25GuangZhoumale
James40ShenZhenmale

如果想要删除某一列,可以使用 pop 方法来完成。

user_info.pop("sex")
user_info
agecity
name
Tom18BeiJing
Bob30ShangHai
Mary25GuangZhou
James40ShenZhen

如果用户的性别不一致的时候,我们可以通过传入一个 like-list 来添加新的一列。

user_info["sex"] = ["male", "male", "female", "male"]
user_info
agecitysex
name
Tom18BeiJingmale
Bob30ShangHaimale
Mary25GuangZhoufemale
James40ShenZhenmale

通过上面的例子可以看出,我们创建新的列的时候都是在原有的 DataFrame 上修改的,也就是说如果添加了新的一列之后,原有的 DataFrame 会发生改变。

如果想要保证原有的 DataFrame 不改变的话,我们可以通过 assign 方法来创建新的一列。

user_info.assign(age_add_one = user_info["age"] + 1)
agecitysexage_add_one
name
Tom18BeiJingmale19
Bob30ShangHaimale31
Mary25GuangZhoufemale26
James40ShenZhenmale41
user_info.assign(sex_code = np.where(user_info["sex"] == "male", 1, 0))
agecitysexsex_code
name
Tom18BeiJingmale1
Bob30ShangHaimale1
Mary25GuangZhoufemale0
James40ShenZhenmale1

4.Pandas常用基本功能

# 导入相关库
import numpy as np
import pandas as pd

常用的基本功能

当我们构建好了 Series 和 DataFrame 之后,我们会经常使用哪些功能呢?来跟我看看吧。引用上一章节中的场景,我们有一些用户的的信息,并将它们存储到了 DataFrame 中。

因为大多数情况下 DataFrame 比 Series 更为常用,所以这里以 DataFrame 举例说明,但实际上很多常用功能对于 Series 也适用。

index = pd.Index(data=["Tom", "Bob", "Mary", "James"], name="name")

data = {
    "age": [18, 30, 25, 40],
    "city": ["BeiJing", "ShangHai", "GuangZhou", "ShenZhen"],
    "sex": ["male", "male", "female", "male"]
}

user_info = pd.DataFrame(data=data, index=index)
user_info
agecitysex
name
Tom18BeiJingmale
Bob30ShangHaimale
Mary25GuangZhoufemale
James40ShenZhenmale

一般拿到数据,我们第一步需要做的是了解下数据的整体情况,可以使用 info 方法来查看。

user_info.info()
Index: 4 entries, Tom to James
Data columns (total 3 columns):
age     4 non-null int64
city    4 non-null object
sex     4 non-null object
dtypes: int64(1), object(2)
memory usage: 128.0+ bytes

如果我们的数据量非常大,我想看看数据长啥样,我当然不希望查看所有的数据了,这时候我们可以采用只看头部的 n 条或者尾部的 n 条。查看头部的 n 条数据可以使用 head 方法,查看尾部的 n 条数据可以使用 tail 方法。

user_info.head(2)
agecitysex
name
Tom18BeiJingmale
Bob30ShangHaimale

此外,Pandas 中的数据结构都有 ndarray 中的常用方法和属性,如通过 .shape 获取数据的形状,通过 .T 获取数据的转置。

user_info.shape
(4, 3)
user_info.T
nameTomBobMaryJames
age18302540
cityBeiJingShangHaiGuangZhouShenZhen
sexmalemalefemalemale

如果我们想要通过 DataFrame 来获取它包含的原有数据,可以通过 .values 来获取,获取后的数据类型其实是一个 ndarray。

user_info.values
array([[18, 'BeiJing', 'male'],
       [30, 'ShangHai', 'male'],
       [25, 'GuangZhou', 'female'],
       [40, 'ShenZhen', 'male']], dtype=object)

描述与统计

有时候我们获取到数据之后,想要查看下数据的简单统计指标(最大值、最小值、平均值、中位数等),比如想要查看年龄的最大值,如何实现呢?

直接对 age 这一列调用 max方法即可。

user_info.age.max()
40

类似的,通过调用 minmeanquantilesum 方法可以实现最小值、平均值、中位数以及求和。可以看到,对一个 Series 调用 这几个方法之后,返回的都只是一个聚合结果。

来介绍个有意思的方法:cumsum,看名字就发现它和 sum 方法有关系,事实上确实如此,cumsum 也是用来求和的,不过它是用来累加求和的,也就是说它得到的结果与原始的 SeriesDataFrame 大小相同。

user_info.age.cumsum()
name
Tom       18
Bob       48
Mary      73
James    113
Name: age, dtype: int64

可以看到,cummax 最后的结果就是将上一次求和的结果与原始当前值求和作为当前值。这话听起来有点绕。举个例子,上面的 73 = 48 + 25cumsum 也可以用来操作字符串类型的对象。

user_info.sex.cumsum()
name
Tom                    male
Bob                malemale
Mary         malemalefemale
James    malemalefemalemale
Name: sex, dtype: object

如果想要获取更多的统计方法,可以参见官方链接:Descriptive statistics

虽然说常见的各种统计值都有对应的方法,如果我想要得到多个指标的话,就需要调用多次方法,是不是显得有点麻烦呢?

Pandas 设计者自然也考虑到了这个问题,想要一次性获取多个统计指标,只需调用 describe方法即可

user_info.describe()
age
count4.000000
mean28.250000
std9.251126
min18.000000
25%23.250000
50%27.500000
75%32.500000
max40.000000

可以看到,直接调用 describe 方法后,会显示出数字类型的列的一些统计指标,如 总数、平均数、标准差、最小值、最大值、25%/50%/75% 分位数。如果想要查看非数字类型的列的统计指标,可以设置 include=["object"] 来获得。

user_info.describe(include=["object"])
citysex
count44
unique42
topBeiJingmale
freq13

上面的结果展示了非数字类型的列的一些统计指标:总数,去重后的个数、最常见的值、最常见的值的频数。

此外,如果我想要统计下某列中每个值出现的次数,如何快速实现呢?调用 value_counts 方法快速获取 Series 中每个值出现的次数。

user_info.sex.value_counts()
male      3
female    1
Name: sex, dtype: int64

如果想要获取某列最大值或最小值对应的索引,可以使用 idxmaxidxmin 方法完成。

user_info.age.idxmax()
'James'

离散化

有时候,我们会碰到这样的需求,想要将年龄进行离散化(分桶),直白来说就是将年龄分成几个区间,这里我们想要将年龄分成 3 个区间段。就可以使用 Pandas 的 cut 方法来完成。

pd.cut(user_info.age, 3)
name
Tom      (17.978, 25.333]
Bob      (25.333, 32.667]
Mary     (17.978, 25.333]
James      (32.667, 40.0]
Name: age, dtype: category
Categories (3, interval[float64]): [(17.978, 25.333] < (25.333, 32.667] < (32.667, 40.0]]

可以看到, cut 自动生成了等距的离散区间,如果自己想定义也是没问题的。

pd.cut(user_info.age, [1, 18, 30, 50])
name
Tom       (1, 18]
Bob      (18, 30]
Mary     (18, 30]
James    (30, 50]
Name: age, dtype: category
Categories (3, interval[int64]): [(1, 18] < (18, 30] < (30, 50]]

有时候离散化之后,想要给每个区间起个名字,可以指定 labels 参数。

pd.cut(user_info.age, [1, 18, 30, 50], labels=["childhood", "youth", "middle"])
name
Tom      childhood
Bob          youth
Mary         youth
James       middle
Name: age, dtype: category
Categories (3, object): [childhood < youth < middle]

除了可以使用 cut 进行离散化之外,qcut 也可以实现离散化。cut 是根据每个值的大小来进行离散化的,qcut 是根据每个值出现的次数来进行离散化的。

pd.qcut(user_info.age, 3)
name
Tom      (17.999, 25.0]
Bob        (25.0, 30.0]
Mary     (17.999, 25.0]
James      (30.0, 40.0]
Name: age, dtype: category
Categories (3, interval[float64]): [(17.999, 25.0] < (25.0, 30.0] < (30.0, 40.0]]

排序功能

在进行数据分析时,少不了进行数据排序。Pandas 支持两种排序方式:按轴(索引或列)排序和按实际值排序。

先来看下按索引排序:sort_index 方法默认是按照索引进行正序排的。

user_info.sort_index()
agecitysex
name
Bob30ShangHaimale
James40ShenZhenmale
Mary25GuangZhoufemale
Tom18BeiJingmale

如果想要按照列进行倒序排,可以设置参数 axis=1ascending=False

user_info.sort_index(axis=1, ascending=False)
sexcityage
name
TommaleBeiJing18
BobmaleShangHai30
MaryfemaleGuangZhou25
JamesmaleShenZhen40

如果想要实现按照实际值来排序,例如想要按照年龄排序,如何实现呢?

使用 sort_values 方法,设置参数 by="age" 即可。

user_info.sort_values(by="age")
agecitysex
name
Tom18BeiJingmale
Mary25GuangZhoufemale
Bob30ShangHaimale
James40ShenZhenmale

有时候我们可能需要按照多个值来排序,例如:按照年龄和城市来一起排序,可以设置参数 by 为一个 list 即可。

注意:list 中每个元素的顺序会影响排序优先级的

user_info.sort_values(by=["age", "city"])
agecitysex
name
Tom18BeiJingmale
Mary25GuangZhoufemale
Bob30ShangHaimale
James40ShenZhenmale

一般在排序后,我们可能需要获取最大的n个值或最小值的n个值,我们可以使用 nlargestnsmallest 方法来完成,这比先进行排序,再使用 head(n) 方法快得多。

user_info.age.nlargest(2)
name
James    40
Bob      30
Name: age, dtype: int64

函数应用

虽说 Pandas 为我们提供了非常丰富的函数,有时候我们可能需要自己定制一些函数,并将它应用到 DataFrame 或 Series。常用到的函数有:mapapplyapplymap

map 是 Series 中特有的方法,通过它可以对 Series 中的每个元素实现转换。

如果我想通过年龄判断用户是否属于中年人(30岁以上为中年),通过 map 可以轻松搞定它。

# 接收一个 lambda 函数
user_info.age.map(lambda x: "yes" if x >= 30 else "no")
name
Tom       no
Bob      yes
Mary      no
James    yes
Name: age, dtype: object

又比如,我想要通过城市来判断是南方还是北方,我可以这样操作。

city_map = {
    "BeiJing": "north",
    "ShangHai": "south",
    "GuangZhou": "south",
    "ShenZhen": "south"
}

# 传入一个 map
user_info.city.map(city_map)
name
Tom      north
Bob      south
Mary     south
James    south
Name: city, dtype: object

apply 方法既支持 Series,也支持 DataFrame,在对 Series 操作时会作用到每个值上,在对 DataFrame 操作时会作用到所有行或所有列(通过 axis 参数控制)。

# 对 Series 来说,apply 方法 与 map 方法区别不大。
user_info.age.apply(lambda x: "yes" if x >= 30 else "no")
name
Tom       no
Bob      yes
Mary      no
James    yes
Name: age, dtype: object
# 对 DataFrame 来说,apply 方法的作用对象是一行或一列数据(一个Series)
user_info.apply(lambda x: x.max(), axis=0)
age           40
city    ShenZhen
sex         male
dtype: object

applymap 方法针对于 DataFrame,它作用于 DataFrame 中的每个元素,它对 DataFrame 的效果类似于 apply 对 Series 的效果。

user_info.applymap(lambda x: str(x).lower())
agecitysex
name
Tom18beijingmale
Bob30shanghaimale
Mary25guangzhoufemale
James40shenzhenmale

修改列/索引名称

在使用 DataFrame 的过程中,经常会遇到修改列名,索引名等情况。使用 rename 轻松可以实现。

修改列名只需要设置参数 columns 即可。

user_info.rename(columns={"age": "Age", "city": "City", "sex": "Sex"})
AgeCitySex
name
Tom18BeiJingmale
Bob30ShangHaimale
Mary25GuangZhoufemale
James40ShenZhenmale

类似的,修改索引名只需要设置参数 index 即可。

user_info.rename(index={"Tom": "tom", "Bob": "bob"})
agecitysex
name
tom18BeiJingmale
bob30ShangHaimale
Mary25GuangZhoufemale
James40ShenZhenmale

类型操作

如果想要获取每种类型的列数的话,可以使用 get_dtype_counts 方法。

user_info.get_dtype_counts()
int64     1
object    2
dtype: int64

如果想要转换数据类型的话,可以通过 astype 来完成。

user_info["age"].astype(float)
name
Tom      18.0
Bob      30.0
Mary     25.0
James    40.0
Name: age, dtype: float64

有时候会涉及到将 object 类型转为其他类型,常见的有转为数字、日期、时间差,Pandas 中分别对应 to_numericto_datetimeto_timedelta 方法。

这里给这些用户都添加一些关于身高的信息。

user_info["height"] = ["178", "168", "178", "180cm"]
user_info
agecitysexheight
name
Tom18BeiJingmale178
Bob30ShangHaimale168
Mary25GuangZhoufemale178
James40ShenZhenmale180cm

现在将身高这一列转为数字,很明显,180cm 并非数字,为了强制转换,我们可以传入 errors 参数,这个参数的作用是当强转失败时的处理方式。

默认情况下,errors='raise',这意味着强转失败后直接抛出异常,设置 errors='coerce'可以在强转失败时将有问题的元素赋值为 pd.NaT(对于datetime和timedelta)或 np.nan(数字)。设置 errors='ignore' 可以在强转失败时返回原有的数据。

python
pd.to_numeric(user_info.height, errors="coerce")
name
Tom      178.0
Bob      168.0
Mary     178.0
James      NaN
Name: height, dtype: float64
pd.to_numeric(user_info.height, errors="ignore")
name
Tom        178
Bob        168
Mary       178
James    180cm
Name: height, dtype: object

5.Pandas缺失值处理

这一章节我们来看下如何使用Pandas处理缺失值。

# 导入相关库
import numpy as np
import pandas as pd

什么是缺失值

在了解缺失值(也叫控制)如何处理之前,首先要知道的就是什么是缺失值?直观上理解,缺失值表示的是“缺失的数据”

可以思考一个问题:是什么原因造成的缺失值呢?其实有很多原因,实际生活中可能由于有的数据不全所以导致数据缺失,也有可能由于误操作导致数据缺失,又或者人为地造成数据缺失。

来看下我们的示例吧。

index = pd.Index(data=["Tom", "Bob", "Mary", "James", "Andy", "Alice"], name="name")

data = {
    "age": [18, 30, np.nan, 40, np.nan, 30],
    "city": ["BeiJing", "ShangHai", "GuangZhou", "ShenZhen", np.nan, " "],
    "sex": [None, "male", "female", "male", np.nan, "unknown"],
    "birth": ["2000-02-10", "1988-10-17", None, "1978-08-08", np.nan, "1988-10-17"]
}

user_info = pd.DataFrame(data=data, index=index)

# 将出生日期转为时间戳
user_info["birth"] = pd.to_datetime(user_info.birth)
user_info
agebirthcitysex
name
Tom18.02000-02-10BeiJingNone
Bob30.01988-10-17ShangHaimale
MaryNaNNaTGuangZhoufemale
James40.01978-08-08ShenZhenmale
AndyNaNNaTNaNNaN
Alice30.01988-10-17unknown

可以看到,用户 Tom 的性别为 None,用户 Mary 的年龄为 NAN,生日为 NaT。在 Pandas 的眼中,这些都属于缺失值,可以使用 isnull()notnull() 方法来操作。

user_info.isnull()
agebirthcitysex
name
TomFalseFalseFalseTrue
BobFalseFalseFalseFalse
MaryTrueTrueFalseFalse
JamesFalseFalseFalseFalse
AndyTrueTrueTrueTrue
AliceFalseFalseFalseFalse

除了简单的可以识别出哪些是缺失值或非缺失值外,最常用的就是过滤掉一些缺失的行。比如,我想过滤掉用户年龄为空的用户,如何操作呢?

user_info[user_info.age.notnull()]
agebirthcitysex
name
Tom18.02000-02-10BeiJingNone
Bob30.01988-10-17ShangHaimale
James40.01978-08-08ShenZhenmale
Alice30.01988-10-17unknown

丢弃缺失值

既然有缺失值了,常见的一种处理办法就是丢弃缺失值。使用 dropna 方法可以丢弃缺失值。

user_info.age.dropna()
name
Tom      18.0
Bob      30.0
James    40.0
Alice    30.0
Name: age, dtype: float64

Seriese 使用 dropna 比较简单,对于 DataFrame 来说,可以设置更多的参数。

axis 参数用于控制行或列,跟其他不一样的是,axis=0 (默认)表示操作行,axis=1 表示操作列。

how 参数可选的值为 any(默认) 或者 allany 表示一行/列有任意元素为空时即丢弃,all 一行/列所有值都为空时才丢弃。

subset 参数表示删除时只考虑的索引或列名。

thresh参数的类型为整数,它的作用是,比如 thresh=3,会在一行/列中至少有 3 个非空值时将其保留。

# 一行数据只要有一个字段存在空值即删除
user_info.dropna(axis=0, how="any")
agebirthcitysex
name
Bob30.01988-10-17ShangHaimale
James40.01978-08-08ShenZhenmale
Alice30.01988-10-17unknown
# 一行数据所有字段都为空值才删除
user_info.dropna(axis=0, how="all")
agebirthcitysex
name
Tom18.02000-02-10BeiJingNone
Bob30.01988-10-17ShangHaimale
MaryNaNNaTGuangZhoufemale
James40.01978-08-08ShenZhenmale
Alice30.01988-10-17unknown
# 一行数据中只要 city 或 sex 存在空值即删除
user_info.dropna(axis=0, how="any", subset=["city", "sex"])
agebirthcitysex
name
Bob30.01988-10-17ShangHaimale
MaryNaNNaTGuangZhoufemale
James40.01978-08-08ShenZhenmale
Alice30.01988-10-17unknown

填充缺失值

除了可以丢弃缺失值外,也可以填充缺失值,最常见的是使用 fillna 完成填充。

fillna 这名字一看就是用来填充缺失值的。

填充缺失值时,常见的一种方式是使用一个标量来填充。例如,这里我样有缺失的年龄都填充为 0。

user_info.age.fillna(0)
name
Tom      18.0
Bob      30.0
Mary      0.0
James    40.0
Andy      0.0
Alice    30.0
Name: age, dtype: float64

除了可以使用标量来填充之外,还可以使用前一个或后一个有效值来填充。

设置参数 method='pad'method='ffill' 可以使用前一个有效值来填充。

user_info.age.fillna(method="ffill")
name
Tom      18.0
Bob      30.0
Mary     30.0
James    40.0
Andy     40.0
Alice    30.0
Name: age, dtype: float64

设置参数 method='bfill'method='backfill' 可以使用后一个有效值来填充。

user_info.age.fillna(method="backfill")
name
Tom      18.0
Bob      30.0
Mary     40.0
James    40.0
Andy     30.0
Alice    30.0
Name: age, dtype: float64

除了通过 fillna 方法来填充缺失值外,还可以通过 interpolate 方法来填充。默认情况下使用线性差值,可以是设置 method 参数来改变方式。

user_info.age.interpolate()
name
Tom      18.0
Bob      30.0
Mary     35.0
James    40.0
Andy     35.0
Alice    30.0
Name: age, dtype: float64

替换缺失值

大家有没有想过一个问题:到底什么才是缺失值呢?你可能会奇怪说,前面不是已经说过了么,Nonenp.nanNaT 这些都是缺失值。但是我也说过了,这些在 Pandas 的眼中是缺失值,有时候在我们人类的眼中,某些异常值我们也会当做缺失值来处理。

例如,在我们的存储的用户信息中,假定我们限定用户都是青年,出现了年龄为 40 的,我们就可以认为这是一个异常值。再比如,我们都知道性别分为男性(male)和女性(female),在记录用户性别的时候,对于未知的用户性别都记为了 “unknown”,很明显,我们也可以认为“unknown”是缺失值。此外,有的时候会出现空白字符串,这些也可以认为是缺失值。

对于上面的这种情况,我们可以使用 replace 方法来替换缺失值。

user_info.age.replace(40, np.nan)
name
Tom      18.0
Bob      30.0
Mary      NaN
James     NaN
Andy      NaN
Alice    30.0
Name: age, dtype: float64

也可以指定一个映射字典。

user_info.age.replace({40: np.nan})
name
Tom      18.0
Bob      30.0
Mary      NaN
James     NaN
Andy      NaN
Alice    30.0
Name: age, dtype: float64

对于 DataFrame,可以指定每列要替换的值。

user_info.replace({"age": 40, "birth": pd.Timestamp("1978-08-08")}, np.nan)
agebirthcitysex
name
Tom18.02000-02-10BeiJingNone
Bob30.01988-10-17ShangHaimale
MaryNaNNaTGuangZhoufemale
JamesNaNNaTShenZhenmale
AndyNaNNaTNaNNaN
Alice30.01988-10-17unknown

类似地,我们可以将特定字符串进行替换,如:将 "unknown" 进行替换。

user_info.sex.replace("unknown", np.nan)
name
Tom        None
Bob        male
Mary     female
James      male
Andy        NaN
Alice       NaN
Name: sex, dtype: object

除了可以替换特定的值之外,还可以使用正则表达式来替换,如:将空白字符串替换成空值。

user_info.city.replace(r'\s+', np.nan, regex=True)
name
Tom        BeiJing
Bob       ShangHai
Mary     GuangZhou
James     ShenZhen
Andy           NaN
Alice          NaN
Name: city, dtype: object

使用其他对象填充

除了我们自己手动丢弃、填充已经替换缺失值之外,我们还可以使用其他对象来填充。

例如有两个关于用户年龄的 Series,其中一个有缺失值,另一个没有,我们可以将没有的缺失值的 Series 中的元素传给有缺失值的。

python
age_new = user_info.age.copy()
age_new.fillna(20, inplace=True)
age_new
name
Tom      18.0
Bob      30.0
Mary     20.0
James    40.0
Andy     20.0
Alice    30.0
Name: age, dtype: float64
user_info.age.combine_first(age_new)
name
Tom      18.0
Bob      30.0
Mary     20.0
James    40.0
Andy     20.0
Alice    30.0
Name: age, dtype: float64

6.Pandas文本数据处理

# 导入相关库
import numpy as np
import pandas as pd

为什么要用str属性

文本数据也就是我们常说的字符串,Pandas 为 Series 提供了 str 属性,通过它可以方便的对每个元素进行操作。

index = pd.Index(data=["Tom", "Bob", "Mary", "James", "Andy", "Alice"], name="name")

data = {
    "age": [18, 30, np.nan, 40, np.nan, 30],
    "city": ["Bei Jing ", "Shang Hai ", "Guang Zhou", "Shen Zhen", np.nan, " "],
    "sex": [None, "male", "female", "male", np.nan, "unknown"],
    "birth": ["2000-02-10", "1988-10-17", None, "1978-08-08", np.nan, "1988-10-17"]
}

user_info = pd.DataFrame(data=data, index=index)

# 将出生日期转为时间戳
user_info["birth"] = pd.to_datetime(user_info.birth)
user_info
agebirthcitysex
name
Tom18.02000-02-10Bei JingNone
Bob30.01988-10-17Shang Haimale
MaryNaNNaTGuang Zhoufemale
James40.01978-08-08Shen Zhenmale
AndyNaNNaTNaNNaN
Alice30.01988-10-17unknown

在之前已经了解过,在对 Series 中每个元素处理时,我们可以使用 mapapply 方法。

比如,我想要将每个城市都转为小写,可以使用如下的方式。

user_info.city.map(lambda x: x.lower())
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-3-e661c5ad5c48> in <module>()
----> 1 user_info.city.map(lambda x: x.lower())


C:\soft\py3\lib\site-packages\pandas\core\series.py in map(self, arg, na_action)
   2156         else:
   2157             # arg is a function
-> 2158             new_values = map_f(values, arg)
   2159 
   2160         return self._constructor(new_values,


pandas/_libs/src\inference.pyx in pandas._libs.lib.map_infer()


<ipython-input-3-e661c5ad5c48> in <lambda>(x)
----> 1 user_info.city.map(lambda x: x.lower())


AttributeError: 'float' object has no attribute 'lower'

What?竟然出错了,错误原因是因为 float 类型的对象没有 lower 属性。这是因为缺失值(np.nan)属于float 类型。

这时候我们的 str 属性操作来了,来看看如何使用吧。

# 将文本转为小写
user_info.city.str.lower()
name
Tom       bei jing 
Bob      shang hai 
Mary     guang zhou
James     shen zhen
Andy            NaN
Alice              
Name: city, dtype: object

可以看到,通过 str 属性来访问之后用到的方法名与 Python 内置的字符串的方法名一样。并且能够自动排除缺失值。

我们再来试试其他一些方法。例如,统计每个字符串的长度。

user_info.city.str.len()
name
Tom       9.0
Bob      10.0
Mary     10.0
James     9.0
Andy      NaN
Alice     1.0
Name: city, dtype: float64

替换和分割

使用 .srt 属性也支持替换与分割操作。

先来看下替换操作,例如:将空字符串替换成下划线。

user_info.city.str.replace(" ", "_")
name
Tom       Bei_Jing_
Bob      Shang_Hai_
Mary     Guang_Zhou
James     Shen_Zhen
Andy            NaN
Alice             _
Name: city, dtype: object

replace 方法还支持正则表达式,例如将所有开头为 S 的城市替换为空字符串。

user_info.city.str.replace("^S.*", " ")
name
Tom       Bei Jing 
Bob                
Mary     Guang Zhou
James              
Andy            NaN
Alice              
Name: city, dtype: object

再来看下分割操作,例如根据空字符串来分割某一列。

user_info.city.str.split(" ")
name
Tom       [Bei, Jing, ]
Bob      [Shang, Hai, ]
Mary      [Guang, Zhou]
James      [Shen, Zhen]
Andy                NaN
Alice              [, ]
Name: city, dtype: object

分割列表中的元素可以使用 get[] 符号进行访问:

user_info.city.str.split(" ").str.get(1)
name
Tom      Jing
Bob       Hai
Mary     Zhou
James    Zhen
Andy      NaN
Alice        
Name: city, dtype: object
user_info.city.str.split(" ").str[1]
name
Tom      Jing
Bob       Hai
Mary     Zhou
James    Zhen
Andy      NaN
Alice        
Name: city, dtype: object

设置参数 expand=True 可以轻松扩展此项以返回 DataFrame。

user_info.city.str.split(" ", expand=True)
012
name
TomBeiJing
BobShangHai
MaryGuangZhouNone
JamesShenZhenNone
AndyNaNNoneNone
AliceNone

提取子串

既然是在操作字符串,很自然,你可能会想到是否可以从一个长的字符串中提取出子串。答案是可以的。

提取第一个匹配的子串

extract 方法接受一个正则表达式并至少包含一个捕获组,指定参数 expand=True 可以保证每次都返回 DataFrame。

例如,现在想要匹配空字符串前面的所有的字母,可以使用如下操作:

user_info.city.str.extract("(\w+)\s+", expand=True)
0
name
TomBei
BobShang
MaryGuang
JamesShen
AndyNaN
AliceNaN

如果使用多个组提取正则表达式会返回一个 DataFrame,每个组只有一列。

例如,想要匹配出空字符串前面和后面的所有字母,操作如下:

user_info.city.str.extract("(\w+)\s+(\w+)", expand=True)
01
name
TomBeiJing
BobShangHai
MaryGuangZhou
JamesShenZhen
AndyNaNNaN
AliceNaNNaN

匹配所有子串

extract 只能够匹配出第一个子串,使用 extractall 可以匹配出所有的子串。

例如,将所有组的空白字符串前面的字母都匹配出来,可以如下操作。

user_info.city.str.extractall("(\w+)\s+")
0
namematch
Tom0Bei
1Jing
Bob0Shang
1Hai
Mary0Guang
James0Shen

测试是否包含子串

除了可以匹配出子串外,我们还可以使用 contains 来测试是否包含子串。例如,想要测试城市是否包含子串 “Zh”。

user_info.city.str.contains("Zh")
name
Tom      False
Bob      False
Mary      True
James     True
Andy       NaN
Alice    False
Name: city, dtype: object

当然了,正则表达式也是支持的。例如,想要测试是否是以字母 “S” 开头。

user_info.city.str.contains("^S")
name
Tom      False
Bob       True
Mary     False
James     True
Andy       NaN
Alice    False
Name: city, dtype: object

生成哑变量

这是一个神奇的功能,通过 get_dummies 方法可以将字符串转为哑变量,sep 参数是指定哑变量之间的分隔符。来看看效果吧。

user_info.city.str.get_dummies(sep=" ")
BeiGuangHaiJingShangShenZhenZhou
name
Tom10010000
Bob00101000
Mary01000001
James00000110
Andy00000000
Alice00000000

这样,它提取出了 Bei, Guang, Hai, Jing, Shang, Shen, Zhen, Zhou 这些哑变量,并对每个变量下使用 0 或 1 来表达。实际上与 One-Hot(狂热编码)是一回事。听不懂没关系,之后将机器学习相关知识时会详细介绍这里。

方法摘要

这里列出了一些常用的方法摘要。

方法描述
cat()连接字符串
split()在分隔符上分割字符串
rsplit()从字符串末尾开始分隔字符串
get()索引到每个元素(检索第i个元素)
join()使用分隔符在系列的每个元素中加入字符串
get_dummies()在分隔符上分割字符串,返回虚拟变量的DataFrame
contains()如果每个字符串都包含pattern / regex,则返回布尔数组
replace()用其他字符串替换pattern / regex的出现
repeat()重复值(s.str.repeat(3)等同于x * 3 t2 >)
pad()将空格添加到字符串的左侧,右侧或两侧
center()相当于str.center
ljust()相当于str.ljust
rjust()相当于str.rjust
zfill()等同于str.zfill
wrap()将长长的字符串拆分为长度小于给定宽度的行
slice()切分Series中的每个字符串
slice_replace()用传递的值替换每个字符串中的切片
count()计数模式的发生
startswith()相当于每个元素的str.startswith(pat)
endswith()相当于每个元素的str.endswith(pat)
findall()计算每个字符串的所有模式/正则表达式的列表
match()在每个元素上调用re.match,返回匹配的组作为列表
extract()在每个元素上调用re.search,为每个元素返回一行DataFrame,为每个正则表达式捕获组返回一列
extractall()在每个元素上调用re.findall,为每个匹配返回一行DataFrame,为每个正则表达式捕获组返回一列
len()计算字符串长度
strip()相当于str.strip
rstrip()相当于str.rstrip
lstrip()相当于str.lstrip
partition()等同于str.partition
rpartition()等同于str.rpartition
lower()相当于str.lower
upper()相当于str.upper
find()相当于str.find
rfind()相当于str.rfind
index()相当于str.index
rindex()相当于str.rindex
capitalize()相当于str.capitalize
swapcase()相当于str.swapcase
normalize()返回Unicode标准格式。相当于unicodedata.normalize
translate()等同于str.translate
isalnum()等同于str.isalnum
isalpha()等同于str.isalpha
isdigit()相当于str.isdigit
isspace()等同于str.isspace
islower()相当于str.islower
isupper()相当于str.isupper
istitle()相当于str.istitle
isnumeric()相当于str.isnumeric
isdecimal()相当于str.isdecimal

7.pandas分类数据处理

来看看 Pandas 中分类(category)数据如何处理吧。

# 导入相关库
import numpy as np
import pandas as pd

创建对象 在创建分类数据之前,先来了解下什么是分类(Category)数据呢?分类数据直白来说就是取值为有限的,或者说是固定数量的可能值。例如:性别、血型。

这里以血型为例,假定每个用户有以下的血型,我们如何创建一个关于血型的分类对象呢?

一种有效的方法就是明确指定 dtype="category"

index = pd.Index(data=["Tom", "Bob", "Mary", "James", "Andy", "Alice"], name="name")

user_info = pd.Series(data=["A", "AB", np.nan, "AB", "O", "B"], index=index, name="blood_type", dtype="category")
user_info
name
Tom        A
Bob       AB
Mary     NaN
James     AB
Andy       O
Alice      B
Name: blood_type, dtype: category
Categories (4, object): [A, AB, B, O]

我们也可以使用 pd.Categorical 来构建分类数据。

pd.Categorical(["A", "AB", np.nan, "AB", "O", "B"]) [A, AB, NaN, AB, O, B] Categories (4, object): [A, AB, B, O] 当然了,我们也可以自己制定类别数据所有可能的取值,假定我们认为血型只有 A、B 以及 AB 这三类,那么我们可以这样操作。

pd.Categorical(["A", "AB", np.nan, "AB", "O", "B"], categories=["A", "B", "AB"])
[A, AB, NaN, AB, NaN, B]
Categories (3, object): [A, B, AB]

除了上面这些方法外,经常遇到的情况是已经创建了一个 Series,如何将它转为分类数据呢?来看看 astype 用法吧。

user_info = pd.Series(data=["A", "AB", np.nan, "AB", "O", "B"], index=index, name="blood_type")
user_info = user_info.astype("category")
user_info
name
Tom        A
Bob       AB
Mary     NaN
James     AB
Andy       O
Alice      B
Name: blood_type, dtype: category
Categories (4, object): [A, AB, B, O]

此外,一些其他的方法返回的结果也是分类数据。如 cut 、 qcut。具体可以见 Pandas基本功能详解中的离散化部分。

常用操作 可以对分类数据使用 .describe() 方法,它得到的结果与 string类型的数据相同。

user_info.describe()
count      5
unique     4
top       AB
freq       2
Name: blood_type, dtype: object

解释下每个指标的含义,count 表示非空的数据有5条,unique 表示去重后的非空数据有4条,top 表示出现次数最多的值为 AB,freq 表示出现次数最多的值的次数为2次。

我们可以使用 .cat.categories 来获取分类数据所有可能的取值。

user_info.cat.categories
Index([u'A', u'AB', u'B', u'O'], dtype='object')

你可能会发现,假如你将分类名称写错了,如何修改呢?难道还需要重新构建一次么?

可以直接使用 .cat.rename_categories 方法来重命名分类名称。

user_info.cat.rename_categories(["A+", "AB+", "B+", "O+"])
name
Tom       A+
Bob      AB+
Mary     NaN
James    AB+
Andy      O+
Alice     B+
Name: blood_type, dtype: category
Categories (4, object): [A+, AB+, B+, O+]

类似的,除了重命名,也会遇到添加类别,删除分类的操作,这些都可以通过 .cat.add_categories ,.cat.remove_categories 来实现。

分类数据也是支持使用 value_counts 方法来查看数据分布的。

user_info.value_counts()
AB    2
O     1
B     1
A     1
Name: blood_type, dtype: int64

分类数据也是支持使用 .str 属性来访问的。例如想要查看下是否包含字母 "A",可以使用 .srt.contains 方法。

user_info.str.contains("A")
name
Tom       True
Bob       True
Mary       NaN
James     True
Andy     False
Alice    False
Name: blood_type, dtype: object

跟多关于 .str 的详细介绍可以见 Pandas文本数据处理。

有时候会遇到合并数据的情况,这时候可以借助 pd.concat 来完成。

blood_type1 = pd.Categorical(["A", "AB"])
blood_type2 = pd.Categorical(["B", "O"])

pd.concat([pd.Series(blood_type1), pd.Series(blood_type2)])
0     A
1    AB
0     B
1     O
dtype: object

可以发现,分类数据经过 pd.concat 合并后类型转为了 object 类型。如果想要保持分类类型的话,可以借助 union_categoricals 来完成。

from pandas.api.types import union_categoricals

union_categoricals([blood_type1, blood_type2])
[A, AB, B, O]
Categories (4, object): [A, AB, B, O]

内存使用量的陷阱 Categorical 的内存使用量是与分类数乘以数据长度成正比,object 类型的数据是一个常数乘以数据的长度。

blood_type = pd.Series(["AB","O"]*1000)
blood_type.nbytes
16000
blood_type.astype("category").nbytes
2016

对比下,是不是发现分类数据非常节省内存。但是当类别的数量接近数据的长度,那么 Categorical 将使用与等效的 object 表示几乎相同或更多的内存。

blood_type = pd.Series(['AB%04d' % i for i in range(2000)])
blood_type.nbytes
16000
blood_type.astype("category").nbytes
20000

8.Pandas时间序列详解

# 导入相关库
import numpy as np
import pandas as pd

在做金融领域方面的分析时,经常会对时间进行一系列的处理。Pandas 内部自带了很多关于时间序列相关的工具,所以它非常适合处理时间序列。在处理时间序列的的过程中,我们经常会去做以下一些任务:

  • 生成固定频率日期和时间跨度的序列
  • 将时间序列整合或转换为特定频率
  • 基于各种非标准时间增量(例如,在一年的最后一个工作日之前的5个工作日)计算“相对”日期,或向前或向后“滚动”日期

使用 Pandas 可以轻松完成以上任务。

基础概述

下面列出了 Pandas中 和时间日期相关常用的类以及创建方法。

备注创建方法
Timestamp时刻数据to_datetime,Timestamp
DatetimeIndexTimestamp的索引to_datetime,date_range,DatetimeIndex
Period时期数据Period
PeriodIndexPeriodperiod_range,PeriodIndex

Pandas 中关于时间序列最常见的类型就是时间戳(Timestamp)了,创建时间戳的方法有很多种,我们分别来看一看。

pd.Timestamp(2018, 5, 21)
Timestamp('2018-05-21 00:00:00')
pd.Timestamp("2018-5-21")
Timestamp('2018-05-21 00:00:00')

除了时间戳之外,另一个常见的结构是时间跨度(Period)。

pd.Period("2018-01")
Period('2018-01', 'M')
pd.Period("2018-05", freq="D")
Period('2018-05-01', 'D')

TimestampPeriod 可以是索引。将TimestampPeriod 作为 SeriesDataFrame的索引后会自动强制转为为 DatetimeIndexPeriodIndex

dates = [pd.Timestamp("2018-05-01"), pd.Timestamp("2018-05-02"), pd.Timestamp("2018-05-03"), pd.Timestamp("2018-05-04")]
ts = pd.Series(data=["Tom", "Bob", "Mary", "James"], index=dates)
ts.index
DatetimeIndex(['2018-05-01', '2018-05-02', '2018-05-03', '2018-05-04'], dtype='datetime64[ns]', freq=None)
periods = [pd.Period("2018-01"), pd.Period("2018-02"), pd.Period("2018-03"), pd.Period("2018-4")]
ts = pd.Series(data=["Tom", "Bob", "Mary", "James"], index=periods)
ts.index
PeriodIndex(['2018-01', '2018-02', '2018-03', '2018-04'], dtype='period[M]', freq='M')

转换时间戳

你可能会想到,我们经常要和文本数据(字符串)打交道,能否快速将文本数据转为时间戳呢?

答案是可以的,通过 to_datetime 能快速将字符串转换为时间戳。当传递一个Series时,它会返回一个Series(具有相同的索引),而类似列表的则转换为DatetimeIndex

pd.to_datetime(pd.Series(["Jul 31, 2018", "2018-05-10", None]))
0   2018-07-31
1   2018-05-10
2          NaT
dtype: datetime64[ns]
pd.to_datetime(["2005/11/23", "2010.12.31"])
DatetimeIndex(['2005-11-23', '2010-12-31'], dtype='datetime64[ns]', freq=None)

除了可以将文本数据转为时间戳外,还可以将 unix 时间转为时间戳。

pd.to_datetime([1349720105, 1349806505, 1349892905], unit="s")
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
               '2012-10-10 18:15:05'],
              dtype='datetime64[ns]', freq=None)
pd.to_datetime([1349720105100, 1349720105200, 1349720105300], unit="ms")
DatetimeIndex(['2012-10-08 18:15:05.100000', '2012-10-08 18:15:05.200000',
               '2012-10-08 18:15:05.300000'],
              dtype='datetime64[ns]', freq=None)

生成时间戳范围

有时候,我们可能想要生成某个范围内的时间戳。例如,我想要生成 "2018-6-26" 这一天之后的8天时间戳,如何完成呢?我们可以使用 date_rangebdate_range 来完成时间戳范围的生成。

pd.date_range("2018-6-26", periods=8)
DatetimeIndex(['2018-06-26', '2018-06-27', '2018-06-28', '2018-06-29',
               '2018-06-30', '2018-07-01', '2018-07-02', '2018-07-03'],
              dtype='datetime64[ns]', freq='D')
pd.bdate_range("2018-6-26", periods=8)
DatetimeIndex(['2018-06-26', '2018-06-27', '2018-06-28', '2018-06-29',
               '2018-07-02', '2018-07-03', '2018-07-04', '2018-07-05'],
              dtype='datetime64[ns]', freq='B')

可以看出,date_range 默认使用的频率是 日历日,而 bdate_range 默认使用的频率是 营业日。当然了,我们可以自己指定频率,比如,我们可以按周来生成时间戳范围。

pd.date_range("2018-6-26", periods=8, freq="W")
DatetimeIndex(['2018-07-01', '2018-07-08', '2018-07-15', '2018-07-22',
               '2018-07-29', '2018-08-05', '2018-08-12', '2018-08-19'],
              dtype='datetime64[ns]', freq='W-SUN')

DatetimeIndex

DatetimeIndex 的主要作用是之一是用作 Pandas 对象的索引,使用它作为索引除了拥有普通索引对象的所有基本功能外,还拥有简化频率处理的高级时间序列方法。

rng = pd.date_range("2018-6-24", periods=4, freq="W")
ts = pd.Series(range(len(rng)), index=rng)
ts
2018-06-24    0
2018-07-01    1
2018-07-08    2
2018-07-15    3
Freq: W-SUN, dtype: int32
# 通过日期访问数据
ts["2018-07-08"]
2
# 通过日期区间访问数据切片
ts["2018-07-08": "2018-07-22"]
2018-07-08    2
2018-07-15    3
Freq: W-SUN, dtype: int32

除了可以将日期作为参数,还可以将年份或者年份、月份作为参数来获取更多的数据。

# 传入年份
ts["2018"]
2018-06-24    0
2018-07-01    1
2018-07-08    2
2018-07-15    3
Freq: W-SUN, dtype: int32
# 传入年份和月份
ts["2018-07"]
2018-07-01    1
2018-07-08    2
2018-07-15    3
Freq: W-SUN, dtype: int32

除了可以使用字符串对 DateTimeIndex 进行索引外,还可以使用 datetime(日期时间)对象来进行索引。

from datetime import datetime

ts[datetime(2018, 7, 8) : datetime(2018, 7, 22)]
2018-07-08    2
2018-07-15    3
Freq: W-SUN, dtype: int32

我们可以通过 TimestampDateTimeIndex 访问一些时间/日期的属性。这里列举一些常见的,想要查看所有的属性见官方链接:Time/Date Components(http://pandas.pydata.org/pandas-docs/stable/timeseries.html#time-date-components)

# 获取年份
ts.index.year
Int64Index([2018, 2018, 2018, 2018], dtype='int64')
# 获取星期几
ts.index.dayofweek
Int64Index([6, 6, 6, 6], dtype='int64')
# 获取一年中的几个第几个星期
ts.index.weekofyear
Int64Index([25, 26, 27, 28], dtype='int64')

DateOffset对象

DateOffset 从名称中就可以看出来是要做日期偏移的,它的参数与 dateutil.relativedelta基本相同,工作方式如下:

from pandas.tseries.offsets import *
d = pd.Timestamp("2018-06-25")
d + DateOffset(weeks=2, days=5)
Timestamp('2018-07-14 00:00:00')

除了可以使用 DateOffset 完成上面的功能外,还可以使用偏移量实例来完成。

d + Week(2) + Day(5)
Timestamp('2018-07-14 00:00:00')

与时间序列相关的方法

在做时间序列相关的工作时,经常要对时间做一些移动/滞后、频率转换、采样等相关操作,我们来看下这些操作如何使用吧。

移动

如果你想移动或滞后时间序列,你可以使用 shift 方法。

ts.shift(2)
2018-06-24    NaN
2018-07-01    NaN
2018-07-08    0.0
2018-07-15    1.0
Freq: W-SUN, dtype: float64

可以看到,Series 所有的值都都移动了 2 个距离。如果不想移动值,而是移动日期索引,可以使用 freq 参数,它可以接受一个 DateOffset 类或其他 timedelta 类对象或一个 offset 别名,所有别名详细介绍见:Offset Aliases(http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases)。

ts.shift(2, freq=Day())
2018-06-26    0
2018-07-03    1
2018-07-10    2
2018-07-17    3
Freq: W-TUE, dtype: int32

可以看到,现在日期索引移动了 2 天的间隔。通过 tshift 同样可以达到相同的效果。

ts.tshift(2, freq=Day())
2018-06-26    0
2018-07-03    1
2018-07-10    2
2018-07-17    3
Freq: W-TUE, dtype: int32

频率转换

频率转换可以使用 asfreq 函数来实现。下面演示了将频率由周转为了天。

ts.asfreq(Day())
2018-06-24    0.0
2018-06-25    NaN
2018-06-26    NaN
2018-06-27    NaN
2018-06-28    NaN
2018-06-29    NaN
2018-06-30    NaN
2018-07-01    1.0
2018-07-02    NaN
2018-07-03    NaN
2018-07-04    NaN
2018-07-05    NaN
2018-07-06    NaN
2018-07-07    NaN
2018-07-08    2.0
2018-07-09    NaN
2018-07-10    NaN
2018-07-11    NaN
2018-07-12    NaN
2018-07-13    NaN
2018-07-14    NaN
2018-07-15    3.0
Freq: D, dtype: float64

聪明的你会发现出现了缺失值,因此 Pandas 为你提供了 method 参数来填充缺失值。几种不同的填充方法参考 Pandas 缺失值处理fillna 介绍。

ts.asfreq(Day(), method="pad")
2018-06-24    0
2018-06-25    0
2018-06-26    0
2018-06-27    0
2018-06-28    0
2018-06-29    0
2018-06-30    0
2018-07-01    1
2018-07-02    1
2018-07-03    1
2018-07-04    1
2018-07-05    1
2018-07-06    1
2018-07-07    1
2018-07-08    2
2018-07-09    2
2018-07-10    2
2018-07-11    2
2018-07-12    2
2018-07-13    2
2018-07-14    2
2018-07-15    3
Freq: D, dtype: int32

重采样

resample 表示根据日期维度进行数据聚合,可以按照分钟、小时、工作日、周、月、年等来作为日期维度,更多的日期维度见 Offset Aliases(http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases)。

这里我们先以月来作为时间维度来进行聚合。

# 求出每个月的数值之和
ts.resample("1M").sum()
2018-06-30    0
2018-07-31    6
Freq: M, dtype: int32
# 求出每个月的数值平均值
ts.resample("1M").mean()
2018-06-30    0
2018-07-31    2
Freq: M, dtype: int32

9.Pandas计算工具介绍

# 导入相关库
import numpy as np
import pandas as pd

Pandas 中包含了非常丰富的计算工具,如一些统计函数、窗口函数、聚合等计算工具。

统计函数

最常见的计算工具莫过于一些统计函数了。这里我们首先构建一个包含了用户年龄与收入的 DataFrame。

index = pd.Index(data=["Tom", "Bob", "Mary", "James", "Andy", "Alice"], name="name")

data = {
    "age": [18, 40, 28, 20, 30, 35],
    "income": [1000, 4500 , 1800, 1800, 3000, np.nan],
}

df = pd.DataFrame(data=data, index=index)
df
ageincome
name
Tom181000.0
Bob404500.0
Mary281800.0
James201800.0
Andy303000.0
Alice35NaN

我们可以通过 cov 函数来求出年龄与收入之间的协方差,计算的时候会丢弃缺失值。

df.age.cov(df.income)
11320.0

除了协方差之外,我们还可以通过 corr 函数来计算下它们之间的相关性,计算的时候会丢弃缺失值。

默认情况下 corr 计算相关性时用到的方法是 pearson,当然了你也可以指定 kendallspearman

df.age.corr(df.income)
0.94416508951340194
df.age.corr(df.income, method="kendall")
0.94868329805051366
df.age.corr(df.income, method="spearman")
0.97467943448089644

除了相关性的计算外,还可以通过 rank 函数求出数据的排名顺序。

df.income.rank()
name
Tom      1.0
Bob      5.0
Mary     2.5
James    2.5
Andy     4.0
Alice    NaN
Name: income, dtype: float64

如果有相同的数,默认取其排名的平均值作为值。我们可以设置参数来得到不同的结果。可以设置的参数有:minmaxfirstdense

df.income.rank(method="first")
name
Tom      1.0
Bob      5.0
Mary     2.0
James    3.0
Andy     4.0
Alice    NaN
Name: income, dtype: float64

窗口函数

有的时候,我们需要对不同窗口的中数据进行一个统计,常见的窗口类型为时间窗口。

例如,下面是某个餐厅 7 天的营业额,我们想要计算每两天的收入总额,如何计算呢?

data = {
    "turnover": [12000, 18000, np.nan, 12000, 9000, 16000, 18000],
    "date": pd.date_range("2018-07-01", periods=7)
}

df2 = pd.DataFrame(data=data)
df2
dateturnover
02018-07-0112000.0
12018-07-0218000.0
22018-07-03NaN
32018-07-0412000.0
42018-07-059000.0
52018-07-0616000.0
62018-07-0718000.0

通过 rolling 我们可以实现,设置 window=2 来保证窗口长度为 2,设置 on="date" 来保证根据日期这一列来滑动窗口(默认不设置,表示根据索引来欢动)

df2.rolling(window=2, on="date").sum()
dateturnover
02018-07-01NaN
12018-07-0230000.0
22018-07-03NaN
32018-07-04NaN
42018-07-0521000.0
52018-07-0625000.0
62018-07-0734000.0

是不是发现,有很多结果是缺失值,导致这个结果的原因是因为在计算时,窗口中默认需要的最小数据个数与窗口长度一致,这里可以设置 min_periods=1 来修改下。

df2.rolling(window=2, on="date", min_periods=1).sum()
dateturnover
02018-07-0112000.0
12018-07-0230000.0
22018-07-0318000.0
32018-07-0412000.0
42018-07-0521000.0
52018-07-0625000.0
62018-07-0734000.0

有时候,我想要计算每段时间的累加和,如何实现呢?先来看看第一种方式吧。

df2.rolling(window=len(df2), on="date", min_periods=1).sum()
dateturnover
02018-07-0112000.0
12018-07-0230000.0
22018-07-0330000.0
32018-07-0442000.0
42018-07-0551000.0
52018-07-0667000.0
62018-07-0785000.0

还有另外一种方式,直接使用 expanding 来生成窗口。

df2.expanding(min_periods=1)["turnover"].sum()
0    12000.0
1    30000.0
2    30000.0
3    42000.0
4    51000.0
5    67000.0
6    85000.0
Name: turnover, dtype: float64

除了可以使用 sum 函数外,还有很多其他的函数可以使用,如:countmeanmedianminmaxstdvarquantileapplycovcorr等等。

方法描述
count()非空观测值数量
sum()值的总和
mean()价值的平均值
median()值的算术中值
min()最小值
max()最大
std()贝塞尔修正样本标准差
var()无偏方差
skew()样品偏斜度(三阶矩)
kurt()样品峰度(四阶矩)
quantile()样本分位数(百分位上的值)
apply()通用适用
cov()无偏协方差(二元)
corr()相关(二进制)

不过上面的方式只能生成一个结果,有时候想要同时求出多个结果(如求和和均值),如何实现呢?

借助 agg 函数可以快速实现。

df2.rolling(window=2, min_periods=1)["turnover"].agg([np.sum, np.mean])
summean
012000.012000.0
130000.015000.0
218000.018000.0
312000.012000.0
421000.010500.0
525000.012500.0
634000.017000.0

如果传入一个字典,可以为生成的统计结果重命名。

df2.rolling(window=2, min_periods=1)["turnover"].agg({"tur_sum": np.sum, "tur_mean": np.mean})
tur_sumtur_mean
012000.012000.0
130000.015000.0
218000.018000.0
312000.012000.0
421000.010500.0
525000.012500.0
634000.017000.0

数学知识补充

均值,方差

统计里最基本的概念就是样本的均值,方差,或者再加个标准差。假定有一个含有n个样本的集合X={X1,…,Xn},依次给出这些概念的公式描述:

很显然,均值描述的是样本集合的中间点,它告诉我们的信息是很有限的。

image.png

而标准差给我们描述的则是样本集合的各个样本点到均值的距离之平均。以这两个集合为例,[0,8,12,20]和[8,9,11,12],两个集合的均值都是10,但显然两个集合差别是很大的,计算两者的标准差,前者是8.3,后者是1.8,显然后者较为集中,故其标准差小一些,标准差描述的就是这种“散布度”。

image.png

看出方差与标准差关系没有?  为什么除以n-1而不是除以n? 这个称为贝塞尔修正。在统计学中样本的均差多是除以自由度(n-1),它的意思是样本能自由选择的程度,当选到只剩一个时,它不可能再有自由了,所以自由度是(n-1)。这样能使我们以较小的样本集更好的逼近总体的标准差,即统计上所谓的“无偏估计”。

下面采用Python演算一下:  参考:https://blog.csdn.net/lyl771857509/article/details/79439184

> import numpy as np
> x=[1,2,3,4]
> print(np.cov(x))</pre>

显示结果: 1.6666666666666665

image.png

协方差

上面几个统计量看似已经描述的差不多了,但我们应该注意到,标准差和方差一般是用来描述一维数据的,但现实生活我们常常遇到含有多维数据的数据集,这个时候怎么办?  协方差该出场了!  协方差可以通俗的理解为:两个变量在变化过程中是同方向变化?还是反方向变化?同向或反向程度如何?

  • 你变大,同时我也变大,说明两个变量是同向变化的,这时协方差就是正的。
  • 你变大,同时我变小,说明两个变量是反向变化的,这时协方差就是负的。  从数值来看,协方差的数值越大,两个变量同向程度也就越大。反之亦然。 image.png

换种说法:  协方差是度量各个维度偏离其均值的程度。协方差的值如果为正值,则说明两者是正相关的,结果为负值就说明负相关的,如果为0,也是就是统计上说的“相互独立”。 与方差对比:  方差是用来度量单个变量“自身变异”大小的总体参数,方差越大表明该变量的变异越大  协方差是用来度量两个变量之间“协同变异”大小的总体参数,即二个变量相互影响大小的参数,协方差的绝对值越大,则二个变量相互影响越大。

采用协方差在线计算器练习一下:    输入值 X=1 ,5 ,6    输入值 Y=4, 2, 9

数目输入 3 X 平均值 4 Y 平均值 5 协方差(X,Y) 4

计算步骤:

总和(X) =1 + 5 + 6 = 12 X平均值 = 4 总和(Y) =4 + 2 + 9 = 15 Y平均值 = 5 协方差(X,Y) = 总和(xi - x平均值)(yi - y平均值)/(采样大小 -1) = (1-4)(4-5)+(5-4)(2-5)+(6-4)(9-5))/2 = 4

为了便于理解和验证,可以参考一下,http://www.ab126.com/shuxue/2788.html所提供的协方差的在线计算器。 image.png

矩阵维数

在分析协方差矩阵之前有必要搞清矩阵维数的概念!以女孩子找对象为例,一般关心几个点 image.png

这里是5个维数。如果同时有几个男孩子备选,则会形成多个行,有对比才有会伤害。

image.png

可以这样形象理解:在女孩心中,多个男孩形成一个个行向量,即多个样本。  另外,再回忆一下系数矩阵的来历。含有n个未知量,由m个方程组成线性方程组的一般形式为: image.png

将系数按它们的位置排列形成一个表格: image.png

这个表格就是方程组的系数矩阵,它的维数是由未知量个数即n来决定的。  下面介绍的协方差矩阵仅与维数有关,和样本数量无关。

协方差矩阵

image.png

image.png

还是有点抽象??? 那就结合实例来理解,可能更方便一些。 假定有下列矩阵: image.png

我们来计算一下协方差矩阵。

X=np.array([[1,4,4,4] ,[5,3,2,7 ],[6,9,9,2]])
print(np.cov(X, rowvar=False))

对于矩阵来说,matlab把每行看做一个观察值,把每列当做一个变量,也就是说对于一个4×3的矩阵求协方差矩阵,matlab会认为存在三个变量,即会求出一个3×3的协方差矩阵。

而Python-NumPy的cov情况略有不同,它默认将每一行视为一个独立的变量,所以在上面的例子中,采用rowvar=False使其视每列为一个变量。

image.png

总和(X) =1 + 5 + 6 = 12 X平均值 = 4 总和(Y) =4 + 3 + 9 = 16 Y平均值 = 5.333 协方差(X,Y) = 总和(xi - x平均值)(yi - y平均值)/(采样大小 -1) = (1-4)(4-5.333)+(5-4)(3-5.333)+(6-4)(9-5.333))/2 = 4.5

image.png

总和(X) =4 + 3 + 9 = 16 X平均值 = 5.333 总和(Y) =4 + 7 + 2 = 13 Y平均值 = 4.333 协方差(X,Y) = 总和(xi - x平均值)(yi - y平均值)/(采样大小 -1) = (4-5.333)(4-4.333)+(3-5.333)(7-4.333)+(9-5.333)(2-4.333))/2 = -7.167

相关系数

相关系数是用以反映变量之间相关关系密切程度的统计指标。相关系数也可以看成协方差:一种剔除了两个变量量纲影响、标准化后的特殊协方差,它消除了两个变量变化幅度的影响,而只是单纯反应两个变量每单位变化时的相似程度。

相关系数绝对值是 小于等于1的。自性关系数为 1 .

image.png

使用NumPy包计算

import numpy as np

# 随机生成两个样本
x = np.random.randint(0, 9, 1000)
y = np.random.randint(0, 9, 1000)

# 计算平均值
mx = x.mean()
my = y.mean()

# 计算标准差
stdx = x.std()
stdy = y.std()

# 计算协方差矩阵
covxy = np.cov(x, y)
print(covxy)

# 我们可以手动进行验证
# covx等于covxy[0, 0], covy等于covxy[1, 1]
# 我们这里的计算结果应该是约等于,因为我们在计算的时候是使用的总体方差(总体方差和样本方差是稍微有点区别的)
covx = np.mean((x - x.mean()) ** 2) 
covy = np.mean((y - y.mean()) ** 2) 
print(covx)
print(covy)

coef0 = covxy[0, 1] / (stdx * stdy)
print(coef0)
# 这里计算的covxy等于上面的covxy[0, 1]和covxy[1, 0],三者相等
covxy = np.mean((x - x.mean()) * (y - y.mean()))
print(covxy)



# 下面计算的是相关系数矩阵(和上面的协方差矩阵是类似的)
coefxy = np.corrcoef(x, y)
print(coefxy)
[[6.85792893 0.00917317]
 [0.00917317 6.88345946]]
6.851071
6.876575999999999
0.0013364546751036613
0.009164000000000101
[[1.         0.00133512]
 [0.00133512 1.        ]]

10.Pandas筛选操作

在数据处理过程中,经常会遇到要筛选不同要求的数据,通过 Pandas 可以轻松时间,这一篇我们来看下如何使用 Pandas 来完成数据筛选吧。

# 导入相关库
import numpy as np
import pandas as pd

Pandas 中除了支持 Python 和 Numpy 的索引运算符[]和属性运算符.来访问数据之外,还有很多其他的方式来访问数据,我们一起来看看吧。

index = pd.Index(data=["Tom", "Bob", "Mary", "James", "Andy", "Alice"], name="name")

data = {
    "age": [18, 30, np.nan, 40, np.nan, 30],
    "city": ["Bei Jing ", "Shang Hai ", "Guang Zhou", "Shen Zhen", np.nan, " "],
    "sex": [None, "male", "female", "male", np.nan, "unknown"],
    "birth": ["2000-02-10", "1988-10-17", None, "1978-08-08", np.nan, "1988-10-17"]
}

user_info = pd.DataFrame(data=data, index=index)

# 将出生日期转为时间戳
user_info["birth"] = pd.to_datetime(user_info.birth)
user_info
nameagebirthcitysex
Tom18.02000-02-10Bei JingNone
Bob30.01988-10-17Shang Haimale
MaryNaNNaTGuang Zhoufemale
James40.01978-08-08Shen Zhenmale
AndyNaNNaTNaNNaN
Alice30.01988-10-17unknown

字典式 get 访问

我们都知道,Python 中的字典要获取 value 时可以通过 get 方法来获取,对于 Series 和 DataFrame 也一样,他们一样可以通过 get 方法来获取。

# 获取得到所有年龄相关的这一列的信息,结果为一个 Series
user_info.get("age")
name
Tom      18.0
Bob      30.0
Mary      NaN
James    40.0
Andy      NaN
Alice    30.0
Name: age, dtype: float64
# 从包含所有的年龄信息的 Series 中得到 Tom 的年龄
user_info.get("age").get("Tom")
18.0

属性访问

除了可以通过 get 方法来获取数据之外,还可以通过属性的方式来访问,同样完成上面的功能,来看下如何通过属性访问的方式来实现。

# 获取得到所有年龄相关的这一列的信息,结果为一个 Series
user_info.age
name
Tom      18.0
Bob      30.0
Mary      NaN
James    40.0
Andy      NaN
Alice    30.0
Name: age, dtype: float64
# 从包含所有的年龄信息的 Series 中得到 Tom 的年龄
user_info.age.Tom
18.0

切片操作

在学习 Python 时,会发现列表的切片操作非常地方便,Series 和 DataFrame 同样也有切片操作。

对于 Series 来说,通过切片可以完成选择指定的行,对于 Series 来说,通过切片可以完成选择指定的行或者列,来看看怎么玩吧。

# 获取年龄的前两行
user_info.age[:2]
name
Tom    18.0
Bob    30.0
Name: age, dtype: float64
# 获取所有信息的前两行
user_info[:2]
nameagebirthcitysex
Tom18.02000-02-10Bei JingNone
Bob30.01988-10-17Shang Haimale
# 所有信息每两行选择一次数据
user_info[::2]
nameagebirthcitysex
Tom18.02000-02-10Bei JingNone
MaryNaNNaTGuang Zhoufemale
AndyNaNNaTNaNNaN
# 对所有信息进行反转
user_info[::-1]
nameagebirthcitysex
Alice30.01988-10-17unknown
AndyNaNNaTNaNNaN
James40.01978-08-08Shen Zhenmale
MaryNaNNaTGuang Zhoufemale
Bob30.01988-10-17Shang Haimale
Tom18.02000-02-10Bei JingNone

上面都是筛选行,如何筛选 DataFrame 中的列呢?

只需要将列名传入切片即可完成筛选。

user_info["age"]
name
Tom      18.0
Bob      30.0
Mary      NaN
James    40.0
Andy      NaN
Alice    30.0
Name: age, dtype: float64

如何筛选出多列的数据呢?只需要将对应的列名传入组成一个列表,传入切片中即可。

user_info[["city", "age"]]
namecityage
TomBei Jing18.0
BobShang Hai30.0
MaryGuang ZhouNaN
JamesShen Zhen40.0
AndyNaNNaN
Alice30.0

可以看到,列表中的列名的顺序会影响最后的结果。

通过数字筛选行和列

通过切片操作可以完成筛选行或者列,如何同时筛选出行和列呢?

通过 iloc 即可实现, iloc 支持传入行和列的筛选器,并用 , 隔开。无论是行或者里筛选器,都可以为以下几种情况:

  • 一个整数,如 2
  • 一个整数列表,如 [2, 1, 4]
  • 一个整数切片对象,如 2:4
  • 一个布尔数组
  • 一个callable

先来看下前3种的用法。

# 筛选出第一行数据
user_info.iloc[0]
age                       18
birth    2000-02-10 00:00:00
city               Bei Jing 
sex                     None
Name: Tom, dtype: object
# 筛选出第二行第一列的数据
user_info.iloc[1, 0]
30.0
# 筛选出第二行、第一行、第三行对应的第一列的数据
user_info.iloc[[1, 0, 2], 0]
name
Bob     30.0
Tom     18.0
Mary     NaN
Name: age, dtype: float64
# 筛选出第一行至第三行以及第一列至第二列的数据
user_info.iloc[0:3, 0:2]
nameagebirth
Tom18.02000-02-10
Bob30.01988-10-17
MaryNaNNaT
# 筛选出第一列至第二列的数据
user_info.iloc[:, 0:2]
nameagebirth
Tom18.02000-02-10
Bob30.01988-10-17
MaryNaNNaT
James40.01978-08-08
AndyNaNNaT
Alice30.01988-10-17

通过名称筛选行和列

虽然通过 iloc 可以实现同时筛选出行和列,但是它接收的是输入,非常不直观, 通过 loc可实现传入名称来筛选数据,loc 支持传入行和列的筛选器,并用 , 隔开。无论是行或者里筛选器,都可以为以下几种情况:

  • 一个索引的名称,如:"Tom"
  • 一个索引的列表,如:["Bob", "Tom"]
  • 一个标签范围,如:"Tom": "Mary"
  • 一个布尔数组
  • 一个callable

先来看下前3种的用法。

# 筛选出名称为 Tom 的数据一行数据
user_info.loc["Tom"]
age                       18
birth    2000-02-10 00:00:00
city               Bei Jing 
sex                     None
Name: Tom, dtype: object
# 筛选出名称为 Tom 的年龄
user_info.loc["Tom", "age"]
18.0
# 筛选出名称在 ["Bob", "Tom"] 中的两行数据
user_info.loc[["Bob", "Tom"]]
nameagebirthcitysex
Bob30.01988-10-17Shang Haimale
Tom18.02000-02-10Bei JingNone
# 筛选出索引名称在 Tom 到 Mary 之间的数据
user_info.loc["Tom": "Mary"]
nameagebirthcitysex
Tom18.02000-02-10Bei JingNone
Bob30.01988-10-17Shang Haimale
MaryNaNNaTGuang Zhoufemale
# 筛选出年龄这一列数据
user_info.loc[:, ["age"]]
nameage
Tom18.0
Bob30.0
MaryNaN
James40.0
AndyNaN
Alice30.0
# 筛选出所有 age 到 birth 之间的这几列数据
user_info.loc[:, "age": "birth"]
nameagebirth
Tom18.02000-02-10
Bob30.01988-10-17
MaryNaNNaT
James40.01978-08-08
AndyNaNNaT
Alice30.01988-10-17

你可能已经发现了,通过名称来筛选时,传入的切片是左右都包含的。

布尔索引

通过布尔操作我们一样可以进行筛选操作,布尔操作时,& 对应 and| 对应 or~ 对应 not

当有多个布尔表达式时,需要通过小括号来进行分组。

user_info[user_info.age > 20]
nameagebirthcitysex
Bob30.01988-10-17Shang Haimale
James40.01978-08-08Shen Zhenmale
Alice30.01988-10-17unknown
# 筛选出年龄在20岁以上,并且性别为男性的数据
user_info[(user_info.age > 20) & (user_info.sex == "male")]
nameagebirthcitysex
Bob30.01988-10-17Shang Haimale
James40.01978-08-08Shen Zhenmale
# 筛选出性别不为 unknown 的数据
user_info[~(user_info.sex == "unknown")]
nameagebirthcitysex
Tom18.02000-02-10Bei JingNone
Bob30.01988-10-17Shang Haimale
MaryNaNNaTGuang Zhoufemale
James40.01978-08-08Shen Zhenmale
AndyNaNNaTNaNNaN

除了切片操作可以实现之外, loc 一样可以实现。

user_info.loc[user_info.age > 20, ["age"]]
nameage
Bob30.0
James40.0
Alice30.0

isin 筛选

Series 包含了 isin 方法,它能够返回一个布尔向量,用于筛选数据。

# 筛选出性别属于 male 和 female的数据
user_info[user_info.sex.isin(["male", "female"])]
nameagebirthcitysex
Bob30.01988-10-17Shang Haimale
MaryNaNNaTGuang Zhoufemale
James40.01978-08-08Shen Zhenmale

对于索引来说,一样可以使用 isin 方法来筛选。

user_info[user_info.index.isin(["Bob"])]
nameagebirthcitysex
Bob30.01988-10-17Shang Haimale

通过Callable筛选

lociloc、切片操作都支持接收一个 callable 函数,callable必须是带有一个参数(调用Series,DataFrame)的函数,并且返回用于索引的有效输出。

user_info[lambda df: df["age"] > 20]
nameagebirthcitysex
Bob30.01988-10-17Shang Haimale
James40.01978-08-08Shen Zhenmale
Alice30.01988-10-17unknown
user_info.loc[lambda df: df.age > 20, lambda df: ["age"]]
nameage
Bob30.0
James40.0
Alice30.0
user_info.iloc[lambda df: [0, 5], lambda df: [0]]
nameage
Tom18.0
Alice30.0

11-Pandas分组聚合

# 导入相关库
import numpy as np
import pandas as pd
index = pd.Index(data=["Tom", "Bob", "Mary", "James", "Andy", "Alice"], name="name")

data = {
    "age": [18, 30, 35, 18, np.nan, 30],
    "city": ["Bei Jing ", "Shang Hai ", "Guang Zhou", "Shen Zhen", np.nan, " "],
    "sex": ["male", "male", "female", "male", np.nan, "female"],
    "income": [3000, 8000, 8000, 4000, 6000, 7000]
}

user_info = pd.DataFrame(data=data, index=index)
user_info
nameagecityincomesex
Tom18.0Bei Jing3000male
Bob30.0Shang Hai8000male
Mary35.0Guang Zhou8000female
James18.0Shen Zhen4000male
AndyNaNNaN6000NaN
Alice30.07000female

将对象分割成组

在进行分组统计前,首先要做的就是进行分组。既然是分组,就需要依赖于某个信息。

比如,依据性别来分组。直接调用 user_info.groupby(user_info["sex"])即可完成按照性别分组。

grouped  = user_info.groupby(user_info["sex"])
grouped.groups
{'female': Index(['Mary', 'Alice'], dtype='object', name='name'),
 'male': Index(['Tom', 'Bob', 'James'], dtype='object', name='name')}

可以看到,已经能够正确的按照性别来进行分组了。通常我们为了更简单,会使用这种方式来实现相同的功能:user_info.groupby("sex")

grouped  = user_info.groupby("sex")
grouped.groups
{'female': Index(['Mary', 'Alice'], dtype='object', name='name'),
 'male': Index(['Tom', 'Bob', 'James'], dtype='object', name='name')}

你可能会想,能不能先按照性别来分组,再按照年龄进一步分组呢?答案是可以的,看这里。

grouped  = user_info.groupby(["sex", "age"])
grouped.groups
{('female', 30.0): Index(['Alice'], dtype='object', name='name'),
 ('female', 35.0): Index(['Mary'], dtype='object', name='name'),
 ('male', 18.0): Index(['Tom', 'James'], dtype='object', name='name'),
 ('male', 30.0): Index(['Bob'], dtype='object', name='name'),
 (nan, nan): Index(['Andy'], dtype='object', name='name')}

关闭排序

默认情况下,groupby 会在操作过程中对数据进行排序。如果为了更好的性能,可以设置 sort=False

grouped  = user_info.groupby(["sex", "age"], sort=False)
grouped.groups
{('female', 30.0): Index(['Alice'], dtype='object', name='name'),
 ('female', 35.0): Index(['Mary'], dtype='object', name='name'),
 ('male', 18.0): Index(['Tom', 'James'], dtype='object', name='name'),
 ('male', 30.0): Index(['Bob'], dtype='object', name='name'),
 (nan, nan): Index(['Andy'], dtype='object', name='name')}

选择列

在使用 groupby 进行分组后,可以使用切片 [] 操作来完成对某一列的选择。

grouped  = user_info.groupby("sex")
grouped
<pandas.core.groupby.DataFrameGroupBy object at 0x000002E355D787B8>
grouped["city"]
<pandas.core.groupby.SeriesGroupBy object at 0x000002E355D78470>

遍历分组

在对数据进行分组后,可以进行遍历。

grouped  = user_info.groupby("sex")

for name, group in grouped:
    print("name: {}".format(name))
    print("group: {}".format(group))
    print("--------------")
name: female
group:         age        city  income     sex
name                                   
Mary   35.0  Guang Zhou    8000  female
Alice  30.0                7000  female
--------------
name: male
group:         age        city  income   sex
name                                 
Tom    18.0   Bei Jing     3000  male
Bob    30.0  Shang Hai     8000  male
James  18.0   Shen Zhen    4000  male
--------------

如果是根据多个字段来分组的,每个组的名称是一个元组。

grouped  = user_info.groupby(["sex", "age"])

for name, group in grouped:
    print("name: {}".format(name))
    print("group: {}".format(group))
    print("--------------")
name: ('female', 30.0)
group:         age city  income     sex
name                            
Alice  30.0         7000  female
--------------
name: ('female', 35.0)
group:        age        city  income     sex
name                                  
Mary  35.0  Guang Zhou    8000  female
--------------
name: ('male', 18.0)
group:         age       city  income   sex
name                                
Tom    18.0  Bei Jing     3000  male
James  18.0  Shen Zhen    4000  male
--------------
name: ('male', 30.0)
group:        age        city  income   sex
name                                
Bob   30.0  Shang Hai     8000  male
--------------

选择一个组

分组后,我们可以通过 get_group 方法来选择其中的某一个组。

grouped  = user_info.groupby("sex")
grouped.get_group("male")
nameagecityincomesex
Tom18.0Bei Jing3000male
Bob30.0Shang Hai8000male
James18.0Shen Zhen4000male
user_info.groupby(["sex", "age"]).get_group(("male", 18))
nameagecityincomesex
Tom18.0Bei Jing3000male
James18.0Shen Zhen4000male

聚合

分组的目的是为了统计,统计的时候需要聚合,所以我们需要在分完组后来看下如何进行聚合。常见的一些聚合操作有:计数、求和、最大值、最小值、平均值等。

想要实现聚合操作,一种方式就是调用 agg 方法。

# 获取不同性别下所包含的人数
grouped = user_info.groupby("sex")
grouped["age"].agg(len)
sex
female    2.0
male      3.0
Name: age, dtype: float64
# 获取不同性别下包含的最大的年龄
grouped = user_info.groupby("sex")
grouped["age"].agg(np.max)
sex
female    35.0
male      30.0
Name: age, dtype: float64

如果是根据多个键来进行聚合,默认情况下得到的结果是一个多层索引结构。

grouped = user_info.groupby(["sex", "age"])
rs = grouped.agg(len)
rs
sexagecityincome
female30.011
35.011
male18.022
30.011

有两种方式可以避免出现多层索引,先来介绍第一种。对包含多层索引的对象调用 reset_index 方法。

rs.reset_index()
sexagecityincome
0female30.011
1female35.011
2male18.022
3male30.011

另外一种方式是在分组时,设置参数 as_index=False

grouped = user_info.groupby(["sex", "age"], as_index=False)
grouped.agg(len)
sexagecityincome
0female30.011
1female35.011
2male18.022
3male30.011

Series 和 DataFrame 都包含了 describe 方法,我们分组后一样可以使用 describe 方法来查看数据的情况。

grouped = user_info.groupby("sex")
grouped.describe()

image.png

一次应用多个聚合操作

有时候进行分组后,不单单想得到一个统计结果,有可能是多个。比如想统计出不同性别下的一个收入的总和和平均值。

grouped = user_info.groupby("sex")
grouped["income"].agg([np.sum, np.mean])
sexsummean
female150007500
male150005000

如果想将统计结果进行重命名,可以传入字典。

grouped = user_info.groupby("sex")
grouped["income"].agg([np.sum, np.mean]).rename(columns={"sum": "income_sum", "mean": "income_mean"})
sexincome_sumincome_mean
female150007500
male150005000

对DataFrame列应用不同的聚合操作

有时候可能需要对不同的列使用不同的聚合操作。例如,想要统计不同性别下人群的年龄的均值以及收入的总和。

grouped = user_info.groupby("sex")
grouped.agg({"age": np.mean, "income": np.sum}).rename(columns={"age": "age_mean", "income": "income_sum"})
sexage_meanincome_sum
female32.515000
male22.015000

transform 操作

前面进行聚合运算的时候,得到的结果是一个以分组名作为索引的结果对象。虽然可以指定 as_index=False ,但是得到的索引也并不是元数据的索引。如果我们想使用原数组的索引的话,就需要进行 merge 转换。

transform方法简化了这个过程,它会把 func 参数应用到所有分组,然后把结果放置到原数组的索引上(如果结果是一个标量,就进行广播)

# 通过 agg 得到的结果的索引是分组名
grouped = user_info.groupby("sex")
grouped["income"].agg(np.mean)
sex
female    7500
male      5000
Name: income, dtype: int64
# 通过 transform 得到的结果的索引是原始索引,它会将得到的结果自动关联上原始的索引
grouped = user_info.groupby("sex")
grouped["income"].transform(np.mean)
name
Tom      5000.0
Bob      5000.0
Mary     7500.0
James    5000.0
Andy        NaN
Alice    7500.0
Name: income, dtype: float64

可以看到,通过 transform 操作得到的结果的长度与原来保持一致。

apply 操作

除了 transform 操作外,还有更神奇的 apply 操作。

apply 会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试用 pd.concat() 把结果组合起来。func 的返回值可以是 Pandas 对象或标量,并且数组对象的大小不限。

# 使用 apply 来完成上面的聚合
grouped = user_info.groupby("sex")
grouped["income"].apply(np.mean)
sex
female    7500.0
male      5000.0
Name: income, dtype: float64

来看下 apply 不一样的用法吧。

比如想要统计不同性别最高收入的前n个值,可以通过下面这种方式实现。

def f1(ser, num=2):
    return ser.nlargest(num).tolist()

grouped["income"].apply(f1)
sex
female    [8000, 7000]
male      [8000, 4000]
Name: income, dtype: object

另外,如果想要获取不同性别下的年龄的均值,通过 apply 可以如下实现。

def f2(df):
    return df["age"].mean()

grouped.apply(f2)
sex
female    32.5
male      22.0
dtype: float64

12-Pandas转换连接

拼接

有两个DataFrame,都存储了用户的一些信息,现在要拼接起来,组成一个DataFrame,如何实现呢?

data1 = {
    "name": ["Tom", "Bob"],
    "age": [18, 30],
    "city": ["Bei Jing ", "Shang Hai "]
}

df1 = pd.DataFrame(data=data1)
df1
agecityname
018Bei JingTom
130Shang HaiBob
data2 = {
    "name": ["Mary", "James"],
    "age": [35, 18],
    "city": ["Guang Zhou", "Shen Zhen"]
}

df2 = pd.DataFrame(data=data2)
df2
agecityname
035Guang ZhouMary
118Shen ZhenJames

append

append 是最简单的拼接两个DataFrame的方法。

df1.append(df2)
agecityname
018Bei JingTom
130Shang HaiBob
035Guang ZhouMary
118Shen ZhenJames

可以看到,拼接后的索引默认还是原有的索引,如果想要重新生成索引的话,设置参数 ignore_index=True 即可。

df1.append(df2, ignore_index=True)
agecityname
018Bei JingTom
130Shang HaiBob
235Guang ZhouMary
318Shen ZhenJames

concat

除了 append 这种方式之外,还有 concat 这种方式可以实现相同的功能。

objs=[df1, df2]
pd.concat(objs, ignore_index=True)
agecityname
018Bei JingTom
130Shang HaiBob
235Guang ZhouMary
318Shen ZhenJames

如果想要区分出不同的DataFrame的数据,可以通过设置参数 keys,当然得设置参数 ignore_index=False

pd.concat(objs, ignore_index=False, keys=["df1", "df2"])
agecityname
df1018Bei JingTom
130Shang HaiBob
df2035Guang ZhouMary
118Shen ZhenJames

关联

有两个DataFrame,分别存储了用户的部分信息,现在需要将用户的这些信息关联起来,如何实现呢?

data1 = {
    "name": ["Tom", "Bob", "Mary", "James"],
    "age": [18, 30, 35, 18],
    "city": ["Bei Jing ", "Shang Hai ", "Guang Zhou", "Shen Zhen"]
}

df1 = pd.DataFrame(data=data1)
df1
agecityname
018Bei JingTom
130Shang HaiBob
235Guang ZhouMary
318Shen ZhenJames
data2 = {"name": ["Bob", "Mary", "James", "Andy"],
        "sex": ["male", "female", "male", np.nan],
         "income": [8000, 8000, 4000, 6000]
}

df2 = pd.DataFrame(data=data2)
df2
incomenamesex
08000Bobmale
18000Maryfemale
24000Jamesmale
36000AndyNaN

merge

通过 pd.merge 可以关联两个DataFrame,这里我们设置参数 on="name",表示依据 name来作为关联键。

pd.merge(df1, df2, on="name")
agecitynameincomesex
030Shang HaiBob8000male
135Guang ZhouMary8000female
218Shen ZhenJames4000male

关联后发现数据变少了,只有 3 行数据,这是因为默认关联的方式是 inner,如果不想丢失任何数据,可以设置参数 how="outer"

pd.merge(df1, df2, on="name", how="outer")
agecitynameincomesex
018.0Bei JingTomNaNNaN
130.0Shang HaiBob8000.0male
235.0Guang ZhouMary8000.0female
318.0Shen ZhenJames4000.0male
4NaNNaNAndy6000.0NaN

可以看到,设置参数 how="outer" 后,确实不会丢失任何数据,他会在不存在的地方填为缺失值。

如果我们想保留左边所有的数据,可以设置参数 how="left";反之,如果想保留右边的所有数据,可以设置参数 how="right"

pd.merge(df1, df2, on="name", how="left")
agecitynameincomesex
018Bei JingTomNaNNaN
130Shang HaiBob8000.0male
235Guang ZhouMary8000.0female
318Shen ZhenJames4000.0male

有时候,两个 DataFrame 中需要关联的键的名称不一样,可以通过 left_onright_on 来分别设置。

df1.rename(columns={"name": "name1"}, inplace=True)
df1
agecityname1
018Bei JingTom
130Shang HaiBob
235Guang ZhouMary
318Shen ZhenJames
df2.rename(columns={"name": "name2"}, inplace=True)
df2
incomename2sex
08000Bobmale
18000Maryfemale
24000Jamesmale
36000AndyNaN
pd.merge(df1, df2, left_on="name1", right_on="name2")
agecityname1incomename2sex
030Shang HaiBob8000Bobmale
135Guang ZhouMary8000Maryfemale
218Shen ZhenJames4000Jamesmale

有时候,两个DataFrame中都包含相同名称的字段,如何处理呢?

我们可以设置参数 suffixes,默认 suffixes=('_x', '_y') 表示将相同名称的左边的DataFrame的字段名加上后缀 _x,右边加上后缀 _y

df1["sex"] = "male"
df1
agecityname1sex
018Bei JingTommale
130Shang HaiBobmale
235Guang ZhouMarymale
318Shen ZhenJamesmale
pd.merge(df1, df2, left_on="name1", right_on="name2")
agecityname1sex_xincomename2sex_y
030Shang HaiBobmale8000Bobmale
135Guang ZhouMarymale8000Maryfemale
218Shen ZhenJamesmale4000Jamesmale
pd.merge(df1, df2, left_on="name1", right_on="name2", suffixes=("_left", "_right"))
agecityname1sex_leftincomename2sex_right
030Shang HaiBobmale8000Bobmale
135Guang ZhouMarymale8000Maryfemale
218Shen ZhenJamesmale4000Jamesmale

join

除了 merge 这种方式外,还可以通过 join 这种方式实现关联。相比 mergejoin 这种方式有以下几个不同:

  • 默认参数on=None,表示关联时使用左边和右边的索引作为键,设置参数on可以指定的是关联时左边的所用到的键名
  • 左边和右边字段名称重复时,通过设置参数 lsuffixrsuffix 来解决。
df1.join(df2.set_index("name2"), on="name1", lsuffix="_left")
agecityname1sex_leftincomesex
018Bei JingTommaleNaNNaN
130Shang HaiBobmale8000.0male
235Guang ZhouMarymale8000.0female
318Shen ZhenJamesmale4000.0male

13-Pandas-IO-操作详解

数据分析过程中经常需要进行读写操作,Pandas实现了很多 IO 操作的API,这里简单做了一个列举。

格式类型数据描述ReaderWriter
textCSVread_csvto_csv
textJSONread_jsonto_json
textHTMLread_htmlto_html
textclipboardread_clipboardto_clipboard
binaryExcelread_excelto_excel
binaryHDF5read_hdfto_hdf
binaryFeatherread_featherto_feather
binaryMsgpackread_msgpackto_msgpack
binaryStataread_statato_stata
binarySASread_sas
binaryPython Pickleread_pickleto_pickle
SQLSQLread_sqlto_sql
SQLGoogle Big Queryread_gbqto_gbq

可以看到,Pandas 的 I/O API是像 pd.read_csv() 一样访问的一组顶级 reader 函数,相应的 writer 函数是像 df.to_csv() 那样访问的对象方法。

这里我们介绍几个常用的API。

# 导入相关库
import numpy as np
import pandas as pd
from io import StringIO

read_csv

读取 csv 文件算是一种最常见的操作了。假如已经有人将一些用户的信息记录在了一个csv文件中,我们如何通过 Pandas 读取呢?

读取之前先来看下这个文件里的内容吧。

!cat ../data/user_info.csv
name,age,birth,sex
Tom,18.0,2000-02-10,
Bob,30.0,1988-10-17,male

可以看到,一共有 4 列,分别是 name, age, birth, sex 。我们可以直接使用 pd.read_csv 来读取。

pd.read_csv("../data/user_info.csv")
nameagebirthsex
0Tom18.02000-02-10NaN
1Bob30.01988-10-17male

可以看到,读取出来生成了一个 DataFrame,索引是自动创建的一个数字,我们可以设置参数 index_col 来将某列设置为索引,可以传入索引号或者名称。

pd.read_csv("../data/user_info.csv", index_col="name")
agebirthsex
name
Tom18.02000-02-10NaN
Bob30.01988-10-17male

除了可以从文件中读取,我们还可以从 StringIO 对象中读取。

data="name,age,birth,sex\nTom,18.0,2000-02-10,\nBob,30.0,1988-10-17,male"
print(data)
name,age,birth,sex
Tom,18.0,2000-02-10,
Bob,30.0,1988-10-17,male
pd.read_csv(StringIO(data))
nameagebirthsex
0Tom18.02000-02-10NaN
1Bob30.01988-10-17male

当然了,你还可以设置参数 sep 来自定义字段之间的分隔符,设置参数 lineterminator 来自定义每行的分隔符。

data = "name|age|birth|sex~Tom|18.0|2000-02-10|~Bob|30.0|1988-10-17|male"

pd.read_csv(StringIO(data), sep="|", lineterminator="~")
nameagebirthsex
0Tom18.02000-02-10NaN
1Bob30.01988-10-17male

在读取时,解析器会进行类型推断,任何非数字列都会以对象dtype的形式出现。当然我们也可以自己指定数据类型。

pd.read_csv(StringIO(data), sep="|", lineterminator="~", dtype={"age": int})
nameagebirthsex
0Tom182000-02-10NaN
1Bob301988-10-17male

Pandas 默认将第一行作为标题,但是有时候,csv文件并没有标题,我们可以设置参数 names来添加标题。

data="Tom,18.0,2000-02-10,\nBob,30.0,1988-10-17,male"
print(data)
Tom,18.0,2000-02-10,
Bob,30.0,1988-10-17,male
pd.read_csv(StringIO(data), names=["name", "age", "birth", "sex"])
nameagebirthsex
0Tom18.02000-02-10NaN
1Bob30.01988-10-17male

有时候可能只需要读取部分列的数据,可以指定参数 user_cols

data="name,age,birth,sex\nTom,18.0,2000-02-10,\nBob,30.0,1988-10-17,male"
print(data)
name,age,birth,sex
Tom,18.0,2000-02-10,
Bob,30.0,1988-10-17,male
pd.read_csv(StringIO(data), usecols=["name", "age"])
nameage
0Tom18.0
1Bob30.0

关于缺失值的处理,也是有技巧的。默认参数 keep_default_na=False,会将空值都填充为 NaN。

pd.read_csv(StringIO(data))
nameagebirthsex
0Tom18.02000-02-10NaN
1Bob30.01988-10-17male
pd.read_csv(StringIO(data), keep_default_na=False)
nameagebirthsex
0Tom18.02000-02-10
1Bob30.01988-10-17male

有时候,空值的定义比较广泛,假定我们认为 18 也是空值,那么将它加入到参数 na_values中即可。

pd.read_csv(StringIO(data), na_values=[18])
nameagebirthsex
0TomNaN2000-02-10NaN
1Bob30.01988-10-17male

了解了 pd.read_csv 如何使用之后,to_csv 就非常方便了,这里就不做介绍了。

to_json

通常在得到了 DataFrame 之后,有时候我们需要将它转为一个 json 字符串,可以使用 to_json 来完成。

转换时,可以通过指定参数 orient 来输出不同格式的格式,之后以下几个参数:

split字典像索引 - > [索引],列 - > [列],数据 - > [值]}
records列表像{[列 - >值},…,{列 - >值}]
index字典像{索引 - > {列 - >值}}
columns字典像{列 - > {索引 - >值}}
values只是值数组

DataFrame 默认情况下使用 columns 这种形式,Series 默认情况下使用 index 这种形式。

设置为 columns 后会将数据作为嵌套JSON对象进行序列化,并将列标签作为主索引。

df = pd.read_csv("../data/user_info.csv", index_col="name")
df
agebirthsex
name
Tom18.02000-02-10NaN
Bob30.01988-10-17male
print(df.to_json())
{"age":{"Tom":18.0,"Bob":30.0},"birth":{"Tom":"2000-02-10","Bob":"1988-10-17"},"sex":{"Tom":null,"Bob":"male"}}

设置为index 后会将数据作为嵌套JSON对象进行序列化,并将索引标签作为主索引。

print(df.to_json(orient="index"))
{"Tom":{"age":18.0,"birth":"2000-02-10","sex":null},"Bob":{"age":30.0,"birth":"1988-10-17","sex":"male"}}

设置为 records 后会将数据序列化为列 - >值记录的JSON数组,不包括索引标签。

print(df.to_json(orient="records"))
[{"age":18.0,"birth":"2000-02-10","sex":null},{"age":30.0,"birth":"1988-10-17","sex":"male"}]

设置为 values 后会将是一个仅用于嵌套JSON数组值,不包含列和索引标签。

print(df.to_json(orient="values"))
[[18.0,"2000-02-10",null],[30.0,"1988-10-17","male"]]

设置为 split 后会将序列化为包含值,索引和列的单独条目的JSON对象。

print(df.to_json(orient="split"))
{"columns":["age","birth","sex"],"index":["Tom","Bob"],"data":[[18.0,"2000-02-10",null],[30.0,"1988-10-17","male"]]}

对于 read_json,这些参数也是同样的道理。

Released under the MIT License.