Pandas的使用(上)

在这一章,我们介绍Pandas这个库的使用,包括Pandas的基本情况,Series和DataFrame这两大数据结构,以及Pandas的文件操作。与NumPy一样,Pandas也是一系列数据分析功能的基础,很多数据分析的工具都是建立在Series和DataFrame格式的数据基础之上。

Pandas基本情况

Pandas是基于NumPy的一个库,是为了解决数据分析任务而创建的,含有使数据清洗和分析工作变得更快更简单的数据结构和操作工具。Pandas这个名字源于panel data(面板数据,这是结构化数据集在计量经济学中的术语)以及Python data analysis(Python数据分析)。

Pandas经常和其它工具一同使用,如数值计算工具NumPy和SciPy,统计分析库statsmodels,机器学习库scikit-learn和数据可视化库matplotlib。Pandas保留了NumPy中基于数组的函数和不使用for循环的数据处理。虽然Pandas采用了大量的NumPy编码风格,但二者最大的不同是Pandas是专门为处理表格和混杂数据设计的,而NumPy更适合处理统一的数值数组。

Pandas由Wes McKinney于2008年开发,当时他任职于AQR Capital Management这家量化投资管理公司。他发现有许多工作需求都不能用任何单一的工具解决:

  • 有标签轴的数据结构,支持自动或清晰的数据对齐。这可以防止由于数据不对齐,或处理来源不同的索引不同的数据,所造成的错误;
  • 集成时间序列功能;
  • 相同的数据结构用于处理时间序列数据和非时间序列数据;
  • 保存元数据的算术运算和压缩;
  • 灵活处理缺失数据;
  • 合并和其它流行数据库(例如基于SQL的数据库)的关系操作。

为此他开发了Pandas,来满足他在工作中的需求。自从2010年Pandas开源以来,Pandas逐渐成长为一个非常大的库,应用于许多真实案例。开发者社区已经有了800个独立的贡献者,他们在解决日常数据问题的同时为这个项目提供贡献。

Pandas使用方式

1.1 Pandas引入方式

Pandas惯用的缩写是pd:

import pandas as pd
import numpy as np
print(pd.__version__)

这里我使用的是0.24.2版本,不同版本的Pandas之间稍有差异。

1.2 Series

Series是一种类似于一维数组的对象,它由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的Series:

s= pd.Series([1, 2, 3])
print(s)

输出为:

0    1
1    2
2    3
dtype: int64

当我们打印Series变量时,可以看到左边有一列表示索引,右边有一列表示具体的数值,下面还有一行表示数值的数据类型。由于我们没有给数据指定索引,Series会自动创建一个从0到N-1(N是数据的长度)的整数型索引。我们也可以通过Series的values和index属性获取对应的ndarray数组和索引对象:

print(s.values)
print(s.index)

输出为:

[1 2 3]
RangeIndex(start=0, stop=3, step=1)

当我们需要指明索引时,可以在创建Series时指明index参数,但是需要注意索引的个数与元素的个数要相同:

s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
print(s)
print(s.index)

输出为:

a    1
b    2
c    3
dtype: int64
Index(['a', 'b', 'c'], dtype='object')

与NumPy数组相比,我们可以通过索引的方式选取Series中的单个或一组值(有点类似字典中按照key取value):

s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
print(s['a'])  # 取出对应索引a的数值1,结果是一个整数值
s['b'] = 5  # 修改索引b对应的值,从2改为5
print(s[['a', 'b', 'c']])  # 通过一个列表取出对应a,b和c这三个索引的元素值,结果仍然是一个Series

输出为:

1
a    1
b    5
c    3
dtype: int64

对一个Series变量使用NumPy函数或类似NumPy的运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引值:

s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
print(s[s > 2])
print(s * 2)
print(np.exp(s))

输出为:

c    3
dtype: int64
a    2
b    4
c    6
dtype: int64
a     2.718282
b     7.389056
c    20.085537
dtype: float64

我们还可以将Series看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。我们可以把Series用到一些需要字典变量的函数中:

s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
print('a' in s)  # 判断a是否在s的索引中,有点类似判断字典是否有某一个key

输出为:

True

既然Series和字典这么类似,我们其实也可以从一个字典创建Series:

d = {'a': 1, 'b': 2, 'c': 3}
s = pd.Series(d)
print(s)

输出为:

a    1
b    2
c    3
dtype: int64

我们也可以用index参数指定顺序:

d = {'a': 1, 'b': 2, 'c': 3}
s = pd.Series(d, index=['b', 'c', 'd'])  # 这里index参数的值不一定要与d.keys()完全一致
print(s)

输出为:

b    2.0
c    3.0
d    NaN
dtype: float64

在上面的结果中a对应的值1没有被用到,而字典中并没有对应键d的值,因此在Series中表示为NaN, not a number,表示缺失值。在pandas中可以使用isnull或者notnull来判断是否有缺失值:

print(pd.isnull(s))
# 或者使用结果反过来的notnull
# print(pd.notnull(s))

输出为:

b    False
c    False
d     True
dtype: bool

为什么要费力加上索引值呢?这样在数据运算过程中就可以自动对齐,如下例所示:

s1 = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
s2 = pd.Series([5, 6, 7, 8], index=['d', 'c', 'b', 'a'])
s3 = s1 + s2
print(s1)
print(s2)
print(s3)

输出为:

a    1
b    2
c    3
d    4
dtype: int64
d    5
c    6
b    7
a    8
dtype: int64
a    9
b    9
c    9
d    9
dtype: int64

Series对象本身及其索引都有一个name属性,在我们指明之前,这些属性都是None:

s = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
print(s)
print(s.name)
print(s.index.name)
print()
s.name = 'test'
s.index.name = 'key'
print(s)

输出为:

a    1
b    2
c    3
d    4
dtype: int64
None
None

key
a    1
b    2
c    3
d    4
Name: test, dtype: int64

我们也可以通过赋值的方式直接修改Series的索引:

s = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
print(s)
s.index = ['d', 'c', 'b', 'a']
print(s)

输出为:

a    1
b    2
c    3
d    4
dtype: int64
d    1
c    2
b    3
a    4
dtype: int64

1.3 DataFrame

DataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame既有行索引也有列索引,它可以被看做由Series组成的字典(共用同一个索引)。DataFrame中的数据在内存中是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。创建DataFrame的办法有很多,最常用的一种是直接传入一个由等长列表或NumPy数组组成的字典:

data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
        'year': [2000, 2001, 2002, 2001, 2002, 2003],
        'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data)
print(frame)

输出为:

    state  year  pop
0    Ohio  2000  1.5
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2001  2.4
4  Nevada  2002  2.9
5  Nevada  2003  3.2

从上面的结果可以看到,跟Series一样,DataFrame会自动加上行索引。另一种常见的数据形式是嵌套字典。外层字典的键作为列索引,内层键则作为行索引:

pop = {'Nevada': {2001: 2.4, 2002: 2.9}, 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
frame = pd.DataFrame(pop)
print(frame)

输出为:

      Nevada  Ohio
2000     NaN   1.5
2001     2.4   1.7
2002     2.9   3.6

对于特别大的DataFrame,head函数默认会选取前五行,或者通过传入参数来指明选取的行数:

data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
        'year': [2000, 2001, 2002, 2001, 2002, 2003],
        'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data)
print(frame.head())
print()
print(frame.head(3))

输出为:

    state  year  pop
0    Ohio  2000  1.5
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2001  2.4
4  Nevada  2002  2.9

  state  year  pop
0  Ohio  2000  1.5
1  Ohio  2001  1.7
2  Ohio  2002  3.6

如果指定了列序列,则DataFrame的列就会按照指定顺序进行排列:

frame = pd.DataFrame(data, columns=['year', 'state', 'pop'])
print(frame)

输出为:

   year   state  pop
0  2000    Ohio  1.5
1  2001    Ohio  1.7
2  2002    Ohio  3.6
3  2001  Nevada  2.4
4  2002  Nevada  2.9
5  2003  Nevada  3.2

如果传入的列在原始数据中不存在,就会产生缺失值:

frame = pd.DataFrame(data, columns=['year', 'state', 'pop', 'size'])
print(frame)

输出为:

   year   state  pop size
0  2000    Ohio  1.5  NaN
1  2001    Ohio  1.7  NaN
2  2002    Ohio  3.6  NaN
3  2001  Nevada  2.4  NaN
4  2002  Nevada  2.9  NaN
5  2003  Nevada  3.2  NaN

我们也可以指明行索引:

frame = pd.DataFrame(data, index=['a', 'b', 'c', 'd', 'e', 'f'])
print(frame)

输出为:

    state  year  pop
a    Ohio  2000  1.5
b    Ohio  2001  1.7
c    Ohio  2002  3.6
d  Nevada  2001  2.4
e  Nevada  2002  2.9
f  Nevada  2003  3.2

当我们想要获取某一列数据的时候,有两种方法:

frame = pd.DataFrame(data, columns=['year', 'state', 'pop'])
print(frame['state'])  # 类似字典按键取值的方法,比较通用
print(frame.year)  # 要求列名符合python的变量命名规则,即由字母、数字、下划线组成,不能以数字开头

输出为:

0      Ohio
1      Ohio
2      Ohio
3    Nevada
4    Nevada
5    Nevada
Name: state, dtype: object
0    2000
1    2001
2    2002
3    2001
4    2002
5    2003
Name: year, dtype: int64

DataFrame的每一列都是一个Series,并且这个Series的name就是对应DataFrame中的列名,索引沿用了DataFrame的行索引。

当我们想要获取一行数据的时候,可以通过位置或名称的方式进行获取,比如用iloc属性:

frame = pd.DataFrame(data, columns=['year', 'state', 'pop'])
print(frame.iloc[0])  # 下标从0开始计数

输出为:

year     2000
state    Ohio
pop       1.5
Name: 0, dtype: object

当我们想要修改DataFrame的一列数值时,可以直接采用赋值的方式将一列数据赋予一个值。如果要赋值的列原来不存在,DataFrame会新建一列:

frame = pd.DataFrame(data, columns=['year', 'state', 'pop'])
print('---before---')
print(frame)
frame['year'] = 2019  # 把year那列所有值改成2019
frame['size'] = 111  # 新建一列size,所有值都是111,另外注意我们没有办法通过frame.size=111的方式新建一列
print('---after---')
print(frame)

输出为:

---before---
   year   state  pop
0  2000    Ohio  1.5
1  2001    Ohio  1.7
2  2002    Ohio  3.6
3  2001  Nevada  2.4
4  2002  Nevada  2.9
5  2003  Nevada  3.2
---after---
   year   state  pop  size
0  2019    Ohio  1.5   111
1  2019    Ohio  1.7   111
2  2019    Ohio  3.6   111
3  2019  Nevada  2.4   111
4  2019  Nevada  2.9   111
5  2019  Nevada  3.2   111

我们也可以通过一个列表或者一个Series更新某一列的值,但是在使用列表时,要求列表的元素个数与每一列包含的元素个数相同;而在使用Series赋值时,则会按照行索引和Series的索引进行对应,如果有缺失值,会表示为NaN:

frame = pd.DataFrame(data, columns=['year', 'state', 'pop'])
print('---before---')
print(frame)
frame['year'] = [2019] * 6
frame['pop'] = pd.Series(np.arange(5))  # 这个Series只包含5个值,默认整数索引为0到4,因此行索引为5的那行数据表示为NaN
print('---after---')
print(frame)

输出为:

---before---
   year   state  pop
0  2000    Ohio  1.5
1  2001    Ohio  1.7
2  2002    Ohio  3.6
3  2001  Nevada  2.4
4  2002  Nevada  2.9
5  2003  Nevada  3.2
---after---
   year   state  pop
0  2019    Ohio  0.0
1  2019    Ohio  1.0
2  2019    Ohio  2.0
3  2019  Nevada  3.0
4  2019  Nevada  4.0
5  2019  Nevada  NaN

del关键字可以用来删除某一列,这种删除会直接体现在我们原本的DataFrame上(而不是某一个副本上):

frame = pd.DataFrame(data, columns=['year', 'state', 'pop'])
print('---before---')
print(frame)
del frame['state']
print('---after---')
print(frame)

输出为:

---before---
   year   state  pop
0  2000    Ohio  1.5
1  2001    Ohio  1.7
2  2002    Ohio  3.6
3  2001  Nevada  2.4
4  2002  Nevada  2.9
5  2003  Nevada  3.2
---after---
   year  pop
0  2000  1.5
1  2001  1.7
2  2002  3.6
3  2001  2.4
4  2002  2.9
5  2003  3.2

类似NumPy的ndarray,我们也可以对DataFrame进行转置,这样行索引和列索引就会换过来:

frame = pd.DataFrame(data, columns=['year', 'state', 'pop'], index=['one', 'two', 'three', 'four', 'five', 'six'])
print('---before---')
print(frame)
print('---after---')
print(frame.T)

输出为:

---before---
       year   state  pop
one    2000    Ohio  1.5
two    2001    Ohio  1.7
three  2002    Ohio  3.6
four   2001  Nevada  2.4
five   2002  Nevada  2.9
six    2003  Nevada  3.2
---after---
        one   two three    four    five     six
year   2000  2001  2002    2001    2002    2003
state  Ohio  Ohio  Ohio  Nevada  Nevada  Nevada
pop     1.5   1.7   3.6     2.4     2.9     3.2

跟Series一样,values属性也会以二维ndarray的形式返回DataFrame中的数据。如果DataFrame各列的数据类型不同,则值数组的dtype就会选用能兼容所有列的数据类型:

frame = pd.DataFrame(data, columns=['year', 'state', 'pop'])
print(frame.values)
print(frame.values.dtype)

输出为:

[[2000 'Ohio' 1.5]
 [2001 'Ohio' 1.7]
 [2002 'Ohio' 3.6]
 [2001 'Nevada' 2.4]
 [2002 'Nevada' 2.9]
 [2003 'Nevada' 3.2]]
object

1.4 索引对象

pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index:

obj = pd.Series(range(3), index=['a', 'b', 'c'])
index = obj.index
print(index)
print(index[1:])  # Index支持类似列表的切片
# 与列表不同,Index是不可变的,这种不可变性可以使Index对象在多个数据结构之间安全共享
# index[1] = 'd'  # 试图修改Index会报错

输出为:

Index(['a', 'b', 'c'], dtype='object')
Index(['b', 'c'], dtype='object')

Pandas的Index支持重复值,即可以包含重复的标签,选择重复的标签,会显示所有的结果:

obj = pd.Series(range(3), index=['a', 'a', 'c'])
print(obj['a'])

输出为:

a    0
a    1
dtype: int64

1.5 数据分析的基本功能

1.5.1 重新索引

pandas的Series和DataFrame的索引都可以都可以通过reindex函数进行重新索引。reindex函数会返回一个新的对象:

obj = pd.Series(range(3), index=['a', 'c', 'b'])
obj1 = obj.reindex(index=['c', 'b', 'a', 'd'])  # 或者直接写成reindex(['c', 'b', 'a', 'd'])
print(obj)  # obj没有被改动
print()
print(obj1)  # 如果某一个索引值不存在,会表示为缺失值

输出为:

a    0
c    1
b    2
dtype: int64

c    1.0
b    2.0
a    0.0
d    NaN
dtype: float64

为了避免NaN,我们也可以用参数method指明填充方法,例如ffill(forward fill):

obj = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
print(obj.reindex(range(6)))
print()
print(obj.reindex(range(6), method='ffill'))

输出为:

0      blue
1       NaN
2    purple
3       NaN
4    yellow
5       NaN
dtype: object

0      blue
1      blue
2    purple
3    purple
4    yellow
5    yellow
dtype: object

或者我们也可以用fill_value参数指明一个填充值:

obj = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
print(obj.reindex(range(6), fill_value='red'))

输出为:

0      blue
1       red
2    purple
3       red
4    yellow
5       red
dtype: object

对DataFrame而言,我们既可以修改行索引,也可以修改列索引:

frame = pd.DataFrame(np.arange(9).reshape((3, 3)),
                     index=['a', 'c', 'd'],
                     columns=['Ohio', 'Texas', 'California'])
print(frame)
print()
frame1 = frame.reindex(index=['a', 'b', 'c', 'd'], columns=['Texas', 'Utah', 'California'])
print(frame1)

输出为:

   Ohio  Texas  California
a     0      1           2
c     3      4           5
d     6      7           8

   Texas  Utah  California
a    1.0   NaN         2.0
b    NaN   NaN         NaN
c    4.0   NaN         5.0
d    7.0   NaN         8.0

1.5.2 丢弃指定轴上的项

我们可以用drop函数删除某一行(列)或者某几行(列)的数据,它会返回一个新对象:

obj = pd.Series(range(3), index=['a', 'b', 'c'])
obj1 = obj.drop('c')
print(obj)  # obj本身没有变化
print()
print(obj1)
print()
del obj1['a']  # 回顾一下前面提到的del关键词,它会直接在obj1上进行操作
print(obj1)

输出为:

a    0
b    1
c    2
dtype: int64

a    0
b    1
dtype: int64

b    1
dtype: int64

对于DataFrame,我们可以用axis指明删除行还是列:

frame = pd.DataFrame(np.arange(16).reshape((4, 4)),
                    index=['Ohio', 'Colorado', 'Utah', 'New York'],
                    columns=['one', 'two', 'three', 'four'])
print(frame)
print()
frame1 = frame.drop(['Ohio', 'Utah'])  # 默认axis=0,即删除一行
print(frame1)
print()
frame2 = frame.drop(['two', 'four'], axis=1)  #或者指明axis='columns'来删除一列
print(frame2)

输出为:

          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

          one  two  three  four
Colorado    4    5      6     7
New York   12   13     14    15

          one  three
Ohio        0      2
Colorado    4      6
Utah        8     10
New York   12     14

如果我们不想创建一个新的对象作为删除后的结果,我们也可以用inplace=True来指明在原来的对象上“原地”操作:

frame.drop(['Ohio', 'Utah'], inplace=True)
print(frame)  # frame发生了变化,不过要小心这种方法对于原始数据的破坏

输出为:

          one  two  three  four
Colorado    4    5      6     7
New York   12   13     14    15

1.5.3 索引、选取和过滤

Series索引(obj[...])的工作方式类似于NumPy数组的索引,只不过Series的索引值不只是代表第几行的整数,还可以是行索引值:

obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
print(obj)
print(obj[1])
print(obj['b'])  # 上面这两行结果相同
print()
print(obj[1:3])
print(obj['b':'d'])  # 小心:这两行结果不一样,这是因为利用标签的切片运算与普通的Python切片运算不同,其末端元素是包含的
print()
print(obj[obj < 2])  # 布尔型索引

输出为:

a    0.0
b    1.0
c    2.0
d    3.0
dtype: float64
1.0
1.0

b    1.0
c    2.0
dtype: float64
b    1.0
c    2.0
d    3.0
dtype: float64

a    0.0
b    1.0
dtype: float64

DataFrame的索引也是类似的:

frame = pd.DataFrame(np.arange(16).reshape((4, 4)),
                     index=['Ohio', 'Colorado', 'Utah', 'New York'],
                     columns=['one', 'two', 'three', 'four'])
print(frame[0:2])
print(frame['Ohio':'Utah'])  # 同样要小心用整数和用行索引得到的结果相差一行
print()
print(frame[['one', 'two']])  # 通过列索引

输出为:

          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11

          one  two
Ohio        0    1
Colorado    4    5
Utah        8    9
New York   12   13

1.5.4 用loc和iloc进行选取

对于DataFrame的标签索引,pandas的开发团队建议用loc和iloc运算符分别处理严格基于标签和整数的索引。

loc通过标签选择一行和多列:

frame = pd.DataFrame(np.arange(16).reshape((4, 4)),
                     index=['Ohio', 'Colorado', 'Utah', 'New York'],
                     columns=['one', 'two', 'three', 'four'])
print(frame)
print()
print(frame.loc['Colorado', ['two', 'three']])  # 通过标签选择一行和多列

输出为:

          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

two      5
three    6
Name: Colorado, dtype: int32

iloc用整数下标进行选取:

frame = pd.DataFrame(np.arange(16).reshape((4, 4)),
                     index=['Ohio', 'Colorado', 'Utah', 'New York'],
                     columns=['one', 'two', 'three', 'four'])
print(frame)
print()
print(frame.iloc[1, [1, 2]])  # 得到上面一样的结果

输出为:

          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

two      5
three    6
Name: Colorado, dtype: int32

这两个索引函数也适用于一个标签或多个标签的切片:

frame = pd.DataFrame(np.arange(16).reshape((4, 4)),
                     index=['Ohio', 'Colorado', 'Utah', 'New York'],
                     columns=['one', 'two', 'three', 'four'])
print(frame)
print()
print(frame.loc[:'Utah', 'two'])  # 挑选到Utah的每一行(包括Utah),two对应的那一列
print()
print(frame.iloc[:, :3][frame.three > 5])  # 先挑选每一行,前3列,然后再挑出第3列大于5的那些行

输出为:

          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

Ohio        1
Colorado    5
Utah        9
Name: two, dtype: int32

          one  two  three
Colorado    4    5      6
Utah        8    9     10
New York   12   13     14

1.5.5 算术运算和数据对齐

前面提到,两个Series相加,会按照索引值进行对齐。如果索引值不完全相同,运算的结果中就会包含缺失值,即NaN:

s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], index=['a', 'c', 'e', 'f', 'g'])
print(s1)
print(s2)
print()
print(s1 + s2)

输出为:

a    7.3
c   -2.5
d    3.4
e    1.5
dtype: float64
a   -2.1
c    3.6
e   -1.5
f    4.0
g    3.1
dtype: float64

a    5.2
c    1.1
d    NaN
e    0.0
f    NaN
g    NaN
dtype: float64

对于DataFrame,对齐操作会同时发生在行和列上:

df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'), index=['Ohio', 'Texas', 'Colorado'])
df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(df1)
print(df2)
print()
print(df1 + df2)

输出为:

            b    c    d
Ohio      0.0  1.0  2.0
Texas     3.0  4.0  5.0
Colorado  6.0  7.0  8.0
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

            b   c     d   e
Colorado  NaN NaN   NaN NaN
Ohio      3.0 NaN   6.0 NaN
Oregon    NaN NaN   NaN NaN
Texas     9.0 NaN  12.0 NaN
Utah      NaN NaN   NaN NaN

我们可能会需要在出现NaN的地方填充一个默认值,例如0。那么我们可以利用add函数和fill_value参数:

a = pd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd'])
b = pd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e'])
print(a)  # a缺失索引值为d和e的值
print(b)  # b缺失索引值为b和e的值
print()
print(a.add(b, fill_value=0))  # b和d的值会被补上0,但是对于a和b都缺失的e的值,在结果中仍然是缺失的

输出为:

a    1.0
b    1.0
c    1.0
d    NaN
dtype: float64
a    1.0
b    NaN
d    1.0
e    NaN
dtype: float64

a    2.0
b    1.0
c    1.0
d    1.0
e    NaN
dtype: float64

1.5.6 DataFrame和Series之间的运算

回顾之前不同维度的NumPy ndarray之间的运算,会采用广播的方式进行补全后再运算。DataFrame和Series之间的运算也是类似的。默认情况下,DataFrame和Series之间的算术运算会将Series的索引匹配到DataFrame的列,然后沿着行一直向下广播:

frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
                     columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series = frame.iloc[0]  # 取出第一行作为Series
print(frame)
print()
print(series)
print()
print(frame - series)

输出为:

          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

b    0.0
d    1.0
e    2.0
Name: Utah, dtype: float64

          b    d    e
Utah    0.0  0.0  0.0
Ohio    3.0  3.0  3.0
Texas   6.0  6.0  6.0
Oregon  9.0  9.0  9.0

如果某个索引值在DataFrame的列或Series的索引中找不到,则参与运算的两个对象就会被重新索引以形成并集:

frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
                     columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series2 = pd.Series(range(3), index=['b', 'e', 'f'])
print(frame)
print()
print(series2)
print()
print(frame + series2)

输出为:

          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

b    0
e    1
f    2
dtype: int64

          b   d     e   f
Utah    0.0 NaN   3.0 NaN
Ohio    3.0 NaN   6.0 NaN
Texas   6.0 NaN   9.0 NaN
Oregon  9.0 NaN  12.0 NaN

如果我们希望匹配行且在列上广播,则必须使用算术运算函数,而不是简单的'+','-'等。例如:

frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
                     columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series3 = frame['d']  # 取出某一列作为Series
print(frame)
print()
print(series3)
print()
print(frame.sub(series3, axis='index'))  # 匹配DataFrame的行索引(axis='index' or axis=0)并进行广播

输出为:

          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

Utah       1.0
Ohio       4.0
Texas      7.0
Oregon    10.0
Name: d, dtype: float64

          b    d    e
Utah   -1.0  0.0  1.0
Ohio   -1.0  0.0  1.0
Texas  -1.0  0.0  1.0
Oregon -1.0  0.0  1.0

1.5.7 函数应用和映射

NumPy的通用函数ufuncs(元素级数组方法)也可用于操作pandas对象:

frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(frame)
print()
print(np.abs(frame))

输出为:

               b         d         e
Utah   -1.301935  0.034308 -0.818884
Ohio   -0.355259  1.772152 -0.319957
Texas   0.085236 -1.096262  0.732062
Oregon  0.143712  0.136601 -0.217775

               b         d         e
Utah    1.301935  0.034308  0.818884
Ohio    0.355259  1.772152  0.319957
Texas   0.085236  1.096262  0.732062
Oregon  0.143712  0.136601  0.217775

另一个常见的操作是,将函数应用到由各列或行所形成的一维数组上。DataFrame的apply方法即可实现此功能。在下面的例子中,函数f计算了一个Series的最大值和最小值的差,在frame的每列都执行了一次。结果是一个Series,使用frame的列作为索引。

f = lambda x: x.max() - x.min()
print(frame.apply(f))

输出为:

b    1.445647
d    2.868414
e    1.550946
dtype: float64

如果传递参数axis='columns'到apply,这个函数会在每行执行:

print(frame.apply(f, axis='columns'))

输出为:

Utah      1.336243
Ohio      2.127410
Texas     1.828324
Oregon    0.361487
dtype: float64

传递到apply的函数不是必须返回一个标量,还可以返回由多个值组成的Series:

def f(x):
    return pd.Series([x.min(), x.max()], index=['min', 'max'])
print(frame.apply(f))

输出为:

            b         d         e
min -1.301935 -1.096262 -0.818884
max  0.143712  1.772152  0.732062

如果我们要对Series中的每个元素应用一个函数,可以用map函数:

format = lambda x: '%.2f' % x  # 格式化字符串,取小数点后两位
print(frame['e'].map(format))  # 取frame的一列(即一个Series)应用map函数

输出为:

Utah      -0.82
Ohio      -0.32
Texas      0.73
Oregon    -0.22
Name: e, dtype: object

如果我们要对DataFrame中的每个元素应用一个函数,可以用applymap函数:

print(frame.applymap(format))

输出为:

            b      d      e
Utah    -1.30   0.03  -0.82
Ohio    -0.36   1.77  -0.32
Texas    0.09  -1.10   0.73
Oregon   0.14   0.14  -0.22

1.5.8 排序和排名

如果我们要对Series和DataFrame按照索引排序,会用到sort_index函数,它会返回一个已经排好序的结果:

obj = pd.Series(range(4), index=['d', 'a', 'b', 'c'])
print(obj.sort_index())

输出为:

a    1
b    2
c    3
d    0
dtype: int64

对于DataFrame,则可以根据任意一个轴上的索引进行排序:

frame = pd.DataFrame(np.arange(8).reshape((2, 4)),
                     index=['three', 'one'],
                     columns=['d', 'a', 'b', 'c'])
print(frame.sort_index())  # 默认按照行索引排序
print()
print(frame.sort_index(axis='columns'))  # 或者axis=1, 指明按照列索引排序

输出为:

       d  a  b  c
one    4  5  6  7
three  0  1  2  3

       a  b  c  d
three  1  2  3  0
one    5  6  7  4

数据默认是按升序排序的,但也可以降序排序:

print(frame.sort_index(axis=1, ascending=False))  # 指明升序的参数ascending为False

输出为:

       d  c  b  a
three  0  3  2  1
one    4  7  6  5

如果要按值排序,则可以使用sort_values函数:

obj = pd.Series([4, 7, -3, 2, np.nan])  # 如果有np.nan,会被放到最后,不管升序还是降序
print(obj.sort_values())
print()
print(obj.sort_values(ascending=False))

输出为:

2   -3.0
3    2.0
0    4.0
1    7.0
4    NaN
dtype: float64

1    7.0
0    4.0
3    2.0
2   -3.0
4    NaN
dtype: float64

当排序一个DataFrame时,我们可能会希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给sort_values的by选项即可达到该目的:

frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})
print(frame.sort_values(by='b'))

输出为:

   b  a
2 -3  0
3  2  1
0  4  0
1  7  1

要根据多个列进行排序,传入名称的列表即可:

print(frame.sort_values(by=['a', 'b']))  # 会优先按照a排序,然后当a相同时,再按照b排序
print()
print(frame.sort_values(by=['b', 'a']))

输出为:

   b  a
2 -3  0
0  4  0
3  2  1
1  7  1

   b  a
2 -3  0
3  2  1
0  4  0
1  7  1

有的时候我们不关心数值的绝对大小,而是关心相对排名,这个时候就会用到rank函数:

obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
print(obj.rank())

输出为:

0    6.5
1    1.0
2    6.5
3    4.5
4    3.0
5    2.0
6    4.5
dtype: float64

默认情况下,会按照从小到大的顺序排出不同数据对应的排名,如果出现数值相同的情况,rank函数默认会给这些数据分配平均排名,因此会出现4.5或6.5这样的排名。我们也可以指明参数method='first'来根据值在原数据中出现的顺序给出排名,例如下面的结果中,数据中标签0位于标签2的前面,之前的两个6.5排名变成了6和7:

obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
print(obj.rank(method='first'))

输出为:

0    6.0
1    1.0
2    7.0
3    4.0
4    3.0
5    2.0
6    5.0
dtype: float64

我们也可以按降序进行排名:

obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
print(obj.rank(ascending=False, method='first'))  # 同样是指明ascending参数

输出为:

0    1.0
1    7.0
2    2.0
3    3.0
4    5.0
5    6.0
6    4.0
dtype: float64

DataFrame可以在行或列上计算排名:

frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1],
                      'c': [-2, 5, 8, -2.5]})
print(frame)
print()
print(frame.rank(axis='columns'))  # frame的每一行进行了排名
print()
print(frame.rank(axis='rows'))  # frame的每一列进行了排名

输出为:

     b  a    c
0  4.3  0 -2.0
1  7.0  1  5.0
2 -3.0  0  8.0
3  2.0  1 -2.5

     b    a    c
0  3.0  2.0  1.0
1  3.0  1.0  2.0
2  1.0  2.0  3.0
3  3.0  2.0  1.0

     b    a    c
0  3.0  1.5  2.0
1  4.0  3.5  3.0
2  1.0  1.5  4.0
3  2.0  3.5  1.0

1.6 汇总和计算描述统计

类似NumPy的ndarray,我们也经常需要对Series和DataFrame数据进行一些统计和计算。跟对应的NumPy数组方法相比,它们都是基于没有缺失数据的假设而构建的。

df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5],
                  [np.nan, np.nan], [0.75, -1.3]],
                  index=['a', 'b', 'c', 'd'],
                  columns=['one', 'two'])
print(df)
print()
print(df.sum())  # 进行求和时会计算非NaN的值

输出为:

    one  two
a  1.40  NaN
b  7.10 -4.5
c   NaN  NaN
d  0.75 -1.3

one    9.25
two   -5.80
dtype: float64

传入axis='columns'或axis=1将会按行进行求和运算:

print(df.sum(axis=1))  # 全部是NaN的第三行结果为0.00

输出为:

a    1.40
b    2.60
c    0.00
d   -0.55
dtype: float64

默认NaN会被跳过,通过skipna选项可以禁用该功能:

print(df.sum(axis=1, skipna=False))

输出为:

a     NaN
b    2.60
c     NaN
d   -0.55
dtype: float64

其他类似的函数还包括min、max等。

还有一些函数会给出累计值,例如累积求和cumsum:

print(df.cumsum())

输出为:

    one  two
a  1.40  NaN
b  8.50 -4.5
c   NaN  NaN
d  9.25 -5.8

还有一种既不是约简型也不是累计型,例如describe函数,它用于一次性产生多个汇总统计:

print(df.describe())

输出为:

            one       two
count  3.000000  2.000000
mean   3.083333 -2.900000
std    3.493685  2.262742
min    0.750000 -4.500000
25%    1.075000 -3.700000
50%    1.400000 -2.900000
75%    4.250000 -2.100000
max    7.100000 -1.300000

也有一些函数会用到不止一列的信息,例如Series的corr方法用于计算两个Series中重叠的、非NA的、按索引对齐的值的相关系数。与此类似,cov用于计算协方差:

print(df['one'].corr(df['two']))
print(df['one'].cov(df['two']))

输出为:

-1.0
-10.16

DataFrame的corr和cov方法将以DataFrame的形式分别返回完整的相关系数或协方差矩阵:

print(df.corr())
print(df.cov())

输出为:

     one  two
one  1.0 -1.0
two -1.0  1.0
           one    two
one  12.205833 -10.16
two -10.160000   5.12

2 数据加载、存储与文件格式

2.1 读写文本格式的数据

2.1.1 读取文本格式的数据

pandas提供了一些用于将表格型数据(例如txt和csv文件)读取为DataFrame对象的函数,其中read_csv和read_table功能差不多,只是默认的分隔符不一样:read_csv的默认分隔符为逗号,read_table的默认分隔符为制表符('\t')。我们之后会用read_csv更多一些。

df = pd.read_csv('examples/ex1.csv')  # ex1.csv文件默认以逗号为分隔符
print(df)
print()
df1 = pd.read_table('examples/ex1.csv', sep=',')  # 如果用read_table函数,指明分隔符为逗号可以实现一样的功能
print(df1)

输出为:

   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

ex1.csv的内容如下:
图片说明

对于没有列名的文件,我们可以让pandas为其分配默认的列名,也可以自己定义列名:

df = pd.read_csv('examples/ex2.csv', header=None)  # header=None是告诉pandas,这个csv的第一行不是列名,从第一行开始就是数据
print(df)
print()
df = pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message'])  # 直接告诉pandas我们要指明列名
print(df)
print()
df = pd.read_csv('examples/ex1.csv', names=['a', 'b', 'c', 'd', 'message'])  # 如果我们对于前面有列名的ex1.csv文件指明列名,就会有问题
print(df)

输出为:

   0   1   2   3      4
0  1   2   3   4  hello
1  5   6   7   8  world
2  9  10  11  12    foo

   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

   a   b   c   d  message
0  a   b   c   d  message
1  1   2   3   4    hello
2  5   6   7   8    world
3  9  10  11  12      foo

ex2.csv的内容如下:
图片说明
我们也可以指明某一列的值作为行索引:

df = pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message'], index_col='message')  # 用index_col指明某一列为行索引
print(df)

输出为:

         a   b   c   d
message               
hello    1   2   3   4
world    5   6   7   8
foo      9  10  11  12

如果希望将多个列做成一个层次化索引,只需传入由列编号或列名组成的列表即可:

df = pd.read_csv('examples/ex3.csv', index_col=['key1', 'key2'])
print(df)
print()
print(df.loc['one'])
print()
print(df.loc['one', 'b'])

输出为:

           value1  value2
key1 key2                
one  a          1       2
     b          3       4
     c          5       6
     d          7       8
two  a          9      10
     b         11      12
     c         13      14
     d         15      16

      value1  value2
key2                
a          1       2
b          3       4
c          5       6
d          7       8

value1    3
value2    4
Name: (one, b), dtype: int64

ex3.csv的内容如下:
图片说明

另外一种常见情况是我们需要跳过csv文件中的某几行,例如注释等等:

df = pd.read_csv('examples/ex4.csv', skiprows=[0, 2, 3])  # 可以用skiprows来指明要跳过的行数
print(df)
print()
# 回顾前面,如果我们对于前面有列名的ex1.csv文件指明列名,可以跳过第一行
df = pd.read_csv('examples/ex1.csv', names=['a', 'b', 'c', 'd', 'e'], skiprows=[0])
print(df)

输出为:

   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

   a   b   c   d      e
0  1   2   3   4  hello
1  5   6   7   8  world
2  9  10  11  12    foo

ex4.csv的内容如下:
图片说明

缺失数据经常是要么没有(空字符串),要么用某个标记值表示。默认情况下,pandas会用一组经常出现的标记值进行识别,比如NA及NULL:

df = pd.read_csv('examples/ex5.csv')
print(df)
print()
df = pd.read_csv('examples/ex5.csv', na_values=['one'])  # 或者我们也可以用na_values补充说明缺失值的情况,这里我们故意把字符串one说成缺失值
print(df)

输出为:

  something  a   b     c   d message
0       one  1   2   3.0   4     NaN
1       two  5   6   NaN   8   world
2     three  9  10  11.0  12     foo

  something  a   b     c   d message
0       NaN  1   2   3.0   4     NaN
1       two  5   6   NaN   8   world
2     three  9  10  11.0  12     foo

ex5.csv的内容如下:
图片说明

2.1.2 逐块读取文本文件

在处理很大的文件时,我们可能会希望先读取文件的开头一部分,或者一块一块地读取。

如果只想读取几行(避免读取整个文件),通过nrows进行指定即可:

df = pd.read_csv('examples/ex1.csv', nrows=1)  # 这里作为演示,只读取第一行
print(df)

输出为:

   a  b  c  d message
0  1  2  3  4   hello

要逐块读取文件,可以指定chunksize(行数),结果会是一个迭代器,可以用于for循环中:

chunker = pd.read_csv('examples/ex1.csv', chunksize=2)  # 这里作为演示,一次读取2行
for df in chunker:
    print(df)
    print()

输出为:

   a  b  c  d message
0  1  2  3  4   hello
1  5  6  7  8   world

   a   b   c   d message
2  9  10  11  12     foo

2.1.3 将数据写出到文本格式

利用pandas的to_csv函数,我们可以很方便地将数据写到一个以逗号分隔的文件中:

data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
        'year': [2000, 2001, 2002, 2001, 2002, 2003],
        'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
df = pd.DataFrame(data)
df.to_csv('examples/ex6.csv')

ex6.csv的内容如下:
图片说明

可以看到默认的情况下,会有行索引和列索引,我们可以分别通过参数index和header控制写出:

df.to_csv('examples/ex7.csv', index=False, header=False)

ex7.csv的内容如下:
图片说明

或者我们也可以按照我们想要的顺序写出某些列:

df.to_csv('examples/ex8.csv', index=False, columns=['year', 'state'])

ex8.csv的内容如下:
图片说明

Series也有类似的to_csv功能:

df['year'].to_csv('examples/ex9.csv')
df['year'].to_csv('examples/ex10.csv', index=False)

ex9.csv的内容如下:
图片说明

ex10.csv的内容如下:
图片说明

2.2 读写二进制格式的数据

2.2.1 读写pickle文件

Python内置的pickle序列化是实现数据的高效二进制格式存储最简单的办法。pandas提供了read_pickle和to_pickle函数:

data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
        'year': [2000, 2001, 2002, 2001, 2002, 2003],
        'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
df = pd.DataFrame(data)
df.to_pickle('examples/ex1.pkl')  # pickle文件的后缀一般是pkl
df1 = pd.read_pickle('examples/ex1.pkl')  # 读取pickle文件
print(df1)

输出为:

    state  year  pop
0    Ohio  2000  1.5
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2001  2.4
4  Nevada  2002  2.9
5  Nevada  2003  3.2

2.2.2 读写Microsoft Excel文件

我们也可以使用read_excel和to_excel函数来实现读写Excel文件:

df = pd.read_excel('examples/ex1.xlsx', 'Sheet1')
print(df)
df.to_excel('examples/ex2.xlsx')

输出为:

   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

ex1.xlsx如下:
图片说明

ex2.xlsx如下:
图片说明

小结

在这一章,我们介绍了Pandas中两种最重要的数据结构,Series和DataFrame的基本使用方法,掌握这两种数据结构对于后续的学习非常重要。同时我们也介绍了Pandas的文件读取和写入操作。在实际应用过程中,我们经常会遇到csv格式的数据文件,Pandas能够很好地处理此类文件,给我们的分析工作带来很大的方便。

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务