03Pandas

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引入约定:

from pandas import Series, DataFrame
import pandas as pd

Python、Numpy和Pandas对比

Python

  • list:Python自带数据类型,主要用一维,功能简单,效率低
  • Dict:Python自带数据类型,多维键值对,效率低

Numpy

  • ndarray:Numpy基础数据类型,单一数据类型
  • 关注数据结构/运算/维度(数据间关系)

Pandas

  • Series:1维,类似带索引的1维ndarray
  • DataFrame:2维,表格型数据类型,类似带行/列索引的2维ndarray
    关注数据与索引的关系(数据实际应用)

从实用性、功能强弱和和可操作性比较:list < ndarray < Series/DataFrame

数据规整和分析工作中,ndarry数组作为必要补充,大部分数据尽量使用Pandas数据类型.

Pandas数据结构

Pandas的核心为两大数据结构,数据分析相关所有事物都是围绕着这两种结构进行的:

  • Series
  • DataFrame
    Series这样的数据结构用于存储一个序列的一维数据,而DataFrame作为更复杂的数据结构,则用于存储多维数据。

虽然这些数据结构不能解决所有的问题,但它们为大多数应用提供了有效和强大的工具。就简洁性而言,他们理解和使用起来都很简单。

两种数据结构奇特之处是讲索引对象和标签整合到自己的结构中,使得两种数据结构都具有很强的可操作性.

Series

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

Series示例

Series的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个0~N-1的整数索引。我们可以通过Series的index和values属性获取索引和值的数组表现形式。

import pandas as pd
pd.Series([i for i in range(1,6)])

输出结果为:

0     1
1     2
2     3
3     4
4     5
dtype: int64

Series基本操作

import pandas as pd
s1 = pd.Series([i for i in range(1,6)])
  • 访问所有的索引
s1.index
RangeIndex(start=0, stop=5, step=1)
  • 访问所有的值
s1.values
array([1, 2, 3, 4, 5])
  • 传入ndarray
s2 = pd.Series(np.arange(10))
s2
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64
  • 传入字典
s3 = pd.Series({'1': 1, '2': 2, '3': 3})
s3
1    1
2    2
3    3
dtype: int64
s3.index
Index(['1', '2', '3'], dtype='object')
s3.values
array([1, 2, 3])
  • 指定index
s4 = pd.Series([1, 2, 3, 4], index=['A', 'B', 'C', 'D'])
s4
A    1
B    2
C    3
D    4
dtype: int64
s4.values
array([1, 2, 3, 4])
s4.index
Index(['A', 'B', 'C', 'D'], dtype='object')
  • Series的访问
s4['A']
1
  • Series的筛选
s4[s4>2]
C    3
D    4
dtype: int64
  • 转化为字典
s4.to_dict()
{'A': 1, 'B': 2, 'C': 3, 'D': 4}
# 如果只传入一个字典,则结果Series中的索引就是原字典的键(有序排列)。你可以传入排好序的字典的键以改变顺序:
s5 = pd.Series(s4.to_dict())
s5
A    1
B    2
C    3
D    4
dtype: int64
index_1 = ['A', 'B', 'C','D','E']
s6 = pd.Series(s5, index = index_1)
s6 #由于E这个索引没有对应的值,即为NaN(即“非数字”(not a number),在pandas中,它用于表示缺失或NA值
A    1.0
B    2.0
C    3.0
D    4.0
E    NaN
dtype: float64
  • 我将使用缺失(missing)或NA表示缺失数据。pandas的isnull和notnull函数可用于检测缺失数据:
pd.isnull(s6)
A    False
B    False
C    False
D    False
E     True
dtype: bool
# Series也有类似的实例方法, 面向对象调用
s6.isnull()
A    False
B    False
C    False
D    False
E     True
dtype: bool
pd.notnull(s6)
A     True
B     True
C     True
D     True
E    False
dtype: bool
  • Series对象本身及其索引都有一个name属性,该属性跟pandas其他的关键功能关系非常密切:
s6.name = 'DEMO1'
s6
A    1.0
B    2.0
C    3.0
D    4.0
E    NaN
Name: DEMO1, dtype: float64
s6.index.name = 'demo_index'
s6
demo_index
A    1.0
B    2.0
C    3.0
D    4.0
E    NaN
Name: DEMO1, dtype: float64
  • Series的索引可以通过赋值的方式就地修改:
s6.index
Index(['A', 'B', 'C', 'D', 'E'], dtype='object', name='demo_index')
import string
up_index = [i for i in string.ascii_lowercase[21:26]]
up_index
['v', 'w', 'x', 'y', 'z']
s6.index = up_index
s6
v    1.0
w    2.0
x    3.0
y    4.0
z    NaN
Name: DEMO1, dtype: float64

Series操作

选取操作

Series对象支持通过以下方式查询数据:

  1. 位置下标 2. 标签索引3. 切片索引 4. 布尔型索引
from pandas import Series
import pandas as pd
series1 = Series([10, 20, 30, 40], index=list('abcd'))
# 通过位置查询
series1[2]
30
# 通过标签索引查询
series1['b']
20
# 查询多个元素
series1[[0, 2, 3]]
series1[['a', 'c', 'd']]
a    10
c    30
d    40
dtype: int64
# 切片
series1[1:3]
series1['b':'d']
b    20
c    30
d    40
dtype: int64
# 布尔索引
series1[series1 > 20]
c    30
d    40
dtype: int64
  • 删除操作

我们队对Series中的元素执行删除,主要用到Series.drop函数和pop函数.drop删除数据之后,返回删除后的副本.Series.pop在删除源数据的元素

series1 = Series([10, 20, 30, 40], index=list('abcd'))
series1
a    10
b    20
c    30
d    40
dtype: int64
# 删除元素返回副本
series1.drop('c')
a    10
b    20
d    40
dtype: int64
series1.drop(['a', 'd'])
b    20
c    30
dtype: int64
# 删除源数据中的元素
series1.pop('d')
40
  • 插入操作
series1 = Series([10, 20, 30, 40], index=list('abcd'))
series2 = Series([100, 200], index=['g', 'h'])
# 新增一个标签索引值为f,值为100的元素
series1['f'] = 100
series1.append(series2)
a     10
b     20
c     30
d     40
f    100
g    100
h    200
dtype: int64
  • Series运算
import numpy as np
series1 = Series([10, 20, 30, 40], index=list('abcd'))
series2 = Series([1, 2, 3, 4], index=list('abce'))
series1 * 2
a    20
b    40
c    60
d    80
dtype: int64
# 两个series运算,会自动对齐运算
series1 * series2
a    10.0
b    40.0
c    90.0
d     NaN
e     NaN
dtype: float64
# 使用Numpy函数
np.sum(series1)
100
# 不对齐运算,将series当做numpy运算
np.add(series1, series2)
a    11
b    22
c    33
d    44
dtype: int64
np.greater(series1, series2)
a    True
b    True
c    True
d    True
dtype: bool

DataFrame

DataFrame这种列表式的数据结构和Excel工作表非常类似,其设计初衷是将Series的使用场景由一维扩展到多维. DataFrame由按一定顺序的多列数据组成,各列的数据类型可以有所不同(数值、字符串、布尔值).


Series对象的Index数组存放有每个元素的标签,而DataFrame对象有所不同,它有两个索引数组。第一个索引数组与行有关,它与Series的索引数组极为相似。 每个标签与标签所在行的所有元素相关联。而第二个数组包含一系列标签,每个标签与一列数据相关联.
DataFrame还可以理解为一个由Series组成的字典,其中每一列的列名为字典的键,每一个Series作为字典的值.

创建DataFrame对象最常用的方法是传递一个dict对象给DataFrame构造函数。DataFrame以dict的键作为列名,每个键都有一个数组作为值. 也可以通过嵌套的dict、list来创建DataFrame对象.

dataframe的创建

  • 通过字典来创建DataFrame
from pandas import DataFrame, Series
import pandas as pd

persons = {
    'name': ['小睿', '小丽', '小明', '小红'],
    'age': [19, 18, 18, 17],
    'sex': ['男', '男', '女', '男'],
}
# 字典的key作为列索引
data_frame1 = DataFrame(persons)
data_frame1
contries = {
    '中国': {'2013': 10, '2014': 20, '2015': 30},
    '阿富汗': {'2013': 12, '2014': 25, '2015': 33},
    '新加坡': {'2013': 11, '2014': 22, '2015': 38},
    '柬埔寨': {'2013': 18, '2014': 16, '2015': 27},
}
# 外层key做列索引,内层key做行索引
data_frame3 = DataFrame(contries)
data_frame3
  • 通过二维Numpy数组创建DataFrame
import numpy as np

ndarray1 = np.random.randint(1, 10, (3, 4))

# 使用默认行索引和列索引
data_frame2 = DataFrame(ndarray1)
data_frame2
  • 通过列表创建DataFrame
data_frame3 = DataFrame([1, 2, 3, 4, 5])
data_frame4 = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
data_frame3
data_frame4

DataFrame索引

# 打印DataFrame行索引
contries = {
    '俄罗斯': {'2013': 10, '2014': 20, '2015': 30},
    '阿富汗': {'2013': 12, '2014': 25, '2015': 33},
    '新加坡': {'2013': 11, '2014': 22, '2015': 38},
    '柬埔寨': {'2013': 18, '2014': 16, '2015': 27},
}
# 外层key做列索引,内层key做行索引
data_frame5 = DataFrame(contries)
data_frame5
  • 行索引
# 行索引
data_frame5.index
Index(['2013', '2014', '2015'], dtype='object')
  • 列索引
# 列索引
data_frame5.columns
Index(['俄罗斯', '阿富汗', '新加坡', '柬埔寨'], dtype='object')
  • 修改行索引
# 修改行索引
data_frame5.index = ['aaa', 'bbb', 'ccc']
data_frame5
# 修改列索引
data_frame5.columns = ['一', '二', '三', '四']
data_frame5

Dataframe的操作

from pandas import DataFrame, Series
import pandas as pd
import numpy as np

contries = {
    '中国': {'2013': 10, '2014': 20, '2015': 30},
    '阿富汗': {'2013': 12, '2014': 25, '2015': 33},
    '新加坡': {'2013': 11, '2014': 22, '2015': 38},
    '柬埔寨': {'2013': 18, '2014': 16, '2015': 27},
}
# 外层key做列索引,内层key做行索引
data_frame1 = DataFrame(contries)
data_frame1
  • 获取元素 获取列数据
# 获取列数据
data_frame1['阿富汗']

返回了一个series

2013    12
2014    25
2015    33
Name: 阿富汗, dtype: int64
# 获取指定多列数据
data_frame1[['柬埔寨', '中国', '阿富汗']]

对于DataFrame的行的标签索引,我引入了特殊的标签运算符loc和iloc。它们可以让你用类似NumPy的标记,使用轴标签(loc)或整数索引(iloc),从DataFrame选择行和列的子集。

  • loc 使用标签索引 loc(行,列)
  • iloc 只能使用整数作为索引 iloc(行,列)
# 获得列切片数据
data_frame1.iloc[:, 1: 3]
# 获取行数据, loc里面可以放字符串标签和布尔值、数组
data_frame1.loc['2014']
中国     20
阿富汗    25
新加坡    22
柬埔寨    16
Name: 2014, dtype: int64
# 获取多行数据
data_frame1.loc[['2013', '2014']]
data_frame1.loc[['2013', '2014'], ['阿富汗','柬埔寨']]
# 查找阿富汗大于15的行
data_frame1.loc[data_frame1.阿富汗 > 15]
# 查找中国大于12的行,保留中国  阿富汗 两列
data_frame1.loc[data_frame1.中国 > 12, ['阿富汗', '中国']]
# 位置下标获取行数据
data_frame1.iloc[0]
中国     10
阿富汗    12
新加坡    11
柬埔寨    18
Name: 2013, dtype: int64
# 切片获得区间数据,以下两种效果一样
data_frame1.iloc[1:]
data_frame1[1:]
# 数组切片索引
data_frame1.iloc[1:, [0, 1]]
# 数组切片索引
data_frame1.iloc[1:, 1:]

Dataframe插入数据

contries = {
    '中国': {'2013': 10, '2014': 20, '2015': 30},
    '阿富汗': {'2013': 12, '2014': 25, '2015': 33},
    '新加坡': {'2013': 11, '2014': 22, '2015': 38},
    '柬埔寨': {'2013': 18, '2014': 16, '2015': 27},
}
# 外层key做列索引,内层key做行索引
data_frame2 = DataFrame(contries)
# 插入一行数据
series1 = Series([100, 200, 300, 400], index=['阿富汗', '柬埔寨', '新加坡', '中国'], name='2019')
series1
阿富汗    100
柬埔寨    200
新加坡    300
中国     400
Name: 2019, dtype: int64
data_frame2.loc['2016'] = 66
data_frame2.loc['2017'] = [23, 24, 25, 26]
data_frame2.loc['2018'] = series1
# append调用之后会产生数据副本
data_frame3 = data_frame2.append(series1)
data_frame3
# 在最后一列增加一列新数据
data_frame3['法兰西'] = Series(np.arange(7), index=[str(x) for x in range(2013, 2020)])

# 在指定位置增加一列
data_frame3.insert(2, '马来西亚', Series(np.random.randint(0, 7, 7), index=[str(x) for x in range(2013, 2020)]))
data_frame3.insert(4, '印度', np.random.randint(0, 7, 7))
data_frame3

Dataframe中删除数据

contries = {
    '中国': {'2013': 10, '2014': 20, '2015': 30},
    '阿富汗': {'2013': 12, '2014': 25, '2015': 33},
    '新加坡': {'2013': 11, '2014': 22, '2015': 38},
    '柬埔寨': {'2013': 18, '2014': 16, '2015': 27},
    '印度': {'2013': 18, '2014': 16, '2015': 27},
    '锡金': {'2013': 18, '2014': 16, '2015': 27},
    '法兰西': {'2013': 18, '2014': 16, '2015': 27},
}
# 外层key做列索引,内层key做行索引
data_frame1 = DataFrame(contries)
data_frame1
# 删除一列数据
del data_frame1['法兰西']

原地操作

data_frame1

原地操作

data_frame1.pop('中国')

2013    10
2014    20
2015    30
Name: 中国, dtype: int64
data_frame1
# 使用drop方法删除多列数据
data_frame2 = data_frame1.drop('锡金', axis=1)  # 0跨行 1跨列
data_frame2 = data_frame1.drop(['新加坡', '印度'], axis=1)  # 0跨行 1跨列
# data_frame2 = data_frame1.drop(['2014', '2015'], axis=0)  # 删除行
data_frame2


关于axis轴的详细介绍请点击https://www.jianshu.com/p/6a97a3ef183a

DataFrame算数运算和数据对齐

pandas能将两个数据结构的索引对齐,这可能是与pandas数据结构索引有关的最强大的功能。这一点尤其体现在数据结构之间的算数运算上.参与运算的两个数据结构,其索引顺序可能不一致,而且有的索引项可能只存在一个数据结构中.

  • Series之间运算,遵循索引对齐
from pandas import Series, DataFrame
import pandas as pd

series1 = Series([10, 20, 30, 40], index=list('abcd'))
series2 = Series([20, 30, 40, 50], index=list('bcde'))
# 如果索引并不重叠,则使用NaN
series1 + series2
a     NaN
b    40.0
c    60.0
d    80.0
e     NaN
dtype: float64
  • DataFrame运算会对齐行和列索引
from pandas import Series, DataFrame
import pandas as pd
import numpy as np

data_frame1 = DataFrame(
    np.arange(12).reshape((3, 4)), 
    index=['fir', 'sec', 'thr'], 
    columns=['a', 'b', 'c', 'd']
)

data_frame2 = DataFrame(
    np.arange(8, 20).reshape((4, 3)), 
    index=['fir', 'sec', 'thr', 'for'], 
    columns=['a', 'b', 'c']
)
data_frame1
data_frame2
data_frame1 + data_frame2
data_frame1.add(data_frame2)
# 在算数方法中填充值,当某个标签在另一个对象中找不到时填充一个值。
data_frame1.add(data_frame2, fill_value=0)
  • DataFrame和Series之间的运算
import numpy as np
data_frame2 = DataFrame(np.arange(9).reshape((3, 3)), 
                        index=['a', 'b', 'c'], 
                        columns=['num1', 'num2', 'num3'])

series2 = Series([10, 20, 30, 40], 
                 index=['num1', 'num2', 'num3', 'num4'])
data_frame2
series2
num1    10
num2    20
num3    30
num4    40
dtype: int64
# DataFrame每一行都加上series
data_frame2 + series2
# DataFrame每一列加上series
series3 = Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
data_frame2.add(series3, axis=0)

pandas中函数应用apply

pandas库以Numpy为基础,并对它的很多功能进行了扩展,用来操作新的数据结构Series和DataFrame.通用函数(ufunc, universal function)就是扩展得到的功能,这类函数能够对数据结构中的元素进行操作,特别有用.除了通用函数,用户还可以自定义函数。

数组中的大多数函数对DataFrame依然有效,因此没有必须要使用apply函数.

from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame1 = DataFrame(np.arange(-8, 8).reshape((4, -1)))
data_frame1
# 计算每一个元素的平方
np.square(data_frame1)
  • 将函数应用到各行或各列所形成的一维数组上
def apply_function(x):
    return np.sum(x)
# 将每一列数据应用到apply_function上
data_frame1.apply(apply_function)
0   -8
1   -4
2    0
3    4
dtype: int64
# 将每一行数据应用到apply_function上
data_frame1.apply(apply_function, axis=1)
0   -26
1   -10
2     6
3    22
dtype: int64
# applymap可以将每一个元素应用到函数中
data_frame1.applymap(lambda x: x + 100)

Pandas排序

根据条件对数据集进行排序也是一种重要的内置运算. 要对行或列索引进行排序,可使用sort_index方法,要根据元素值来排序,可使用sort_values.

def sort_index(self, axis=0, ascending=True, inplace=False):
    pass

def sort_values(self, by, axis=0, ascending=True, inplace=False):
    pass
  • axis: 按行还是按列排序.

  • ascending: 升序还是降序,默认True是升序排列,False降序排序.

  • inplace: 默认False, 返回排序副本. True表示直接修改序列本身

  • 对Series排序

from pandas import DataFrame, Series
import pandas as pd
import numpy as np

series1 = Series(np.random.randint(1, 100, 4), index=['d', 'a', 'c', 'b'])
series1
d    81
a    85
c    33
b    44
dtype: int64
# 根据索引升序排列
series1.sort_index()
a    85
b    44
c    33
d    81
dtype: int64
# 根据索引降序排列
series1.sort_index(ascending=False, inplace=True)
series1
d    81
c    33
b    44
a    85
dtype: int64
# 按照值对Series升序排列
series1.sort_values()
c    33
b    44
d    81
a    85
dtype: int64
# 按照值对Series降序排列
series1.sort_values(ascending=False)
a    85
d    81
b    44
c    33
dtype: int64
  • 对DataFrame排序
data_frame1 = DataFrame(
    np.arange(16).reshape((4, 4)),
    index=['b', 'a', 'd', 'c'],
    columns=[4, 2, 3, 1]
)
data_frame1
# 根据行索引进行排序
data_frame1.sort_index()
# 根据行索引进行降序排列
data_frame1.sort_index(ascending=False)
# 根据列索引排序
data_frame1.sort_index(axis=1)
# 根据列索引降序排列
data_frame1.sort_index(ascending=False, axis=1, inplace=True)
data_frame1
# 按照值对行进行升序排列
data_frame1.sort_values(by=1)
# 按照值对行进行降序排列
data_frame1.sort_values(by=1, ascending=False)
# 按照值对指定列进行升序排列
data_frame1.sort_values(by='a', axis=1)
# 按照值对指定列进行降序排列
data_frame1.sort_values(by='a', axis=1, ascending=False)

等级索引和分级

等级索引是pandas的一个重要功能,单条轴可以有多级索引. 你可以像操作两位结构那样处理多维数据.

from pandas import Series, DataFrame
import pandas as pd
import numpy as np

areas_economy = Series(
    np.arange(10, 100, 10),
    index=[
        ['北京市', '北京市', '北京市', '河北省', '河北省', '河北省', '山东省', '山东省', '山东省'],
        ['信息业', '制造业', '服务业', '信息业', '制造业', '服务业', '信息业', '制造业', '服务业']])

areas_economy
北京市  信息业    10
     制造业    20
     服务业    30
河北省  信息业    40
     制造业    50
     服务业    60
山东省  信息业    70
     制造业    80
     服务业    90
dtype: int64
  • 层级索引取值和索引sort_index排序
# 1. 获得一个省的信息
areas_economy['山东省']
信息业    70
制造业    80
服务业    90
dtype: int64
# 2. 获得多个省信息
areas_economy[['河北省', '山东省']]
河北省  信息业    40
     制造业    50
     服务业    60
山东省  信息业    70
     制造业    80
     服务业    90
dtype: int64
# 3. 获得所有省的制造业信息
areas_economy[:, '制造业']
# 获得多个省制造业信息错误写法
# areas_economy[['河北省', '山东省'], '制造业']
北京市    20
河北省    50
山东省    80
dtype: int64
# 4. 获得多个省制造业信息
areas_economy.loc[['河北省', '山东省'], '制造业']
河北省  制造业    50
山东省  制造业    80
dtype: int64
# 5. 获得多个省制造业、服务业信息
areas_economy.loc[['河北省', '山东省'], ['制造业', '服务业']]
河北省  制造业    50
     服务业    60
山东省  制造业    80
     服务业    90
dtype: int64
# 需要对最外层排序之后才可执行切片索引
areas_economy.sort_index()['北京市':'山东省']
北京市  信息业    10
     制造业    20
     服务业    30
山东省  信息业    70
     制造业    80
     服务业    90
dtype: int64
# 交换索引
areas_economy.swaplevel()
信息业  北京市    10
制造业  北京市    20
服务业  北京市    30
信息业  河北省    40
制造业  河北省    50
服务业  河北省    60
信息业  山东省    70
制造业  山东省    80
服务业  山东省    90
dtype: int64
# 对索引进行排序, 可通过level=0或1指定根据那层索引排序
areas_economy.sort_index(level=0)
北京市  信息业    10
     制造业    20
     服务业    30
山东省  信息业    70
     制造业    80
     服务业    90
河北省  信息业    40
     制造业    50
     服务业    60
dtype: int64
  • 按层级统计数据
# 计算北京市经济总量
areas_economy['北京市'].sum()
# 计算山东省三大行业的平均经济总量
areas_economy['山东省'].mean()
80.0
# 统计每个行业的经济总量
areas_economy.sum(level=1)
信息业    120
制造业    150
服务业    180
dtype: int64
data_frame1 = DataFrame(
    np.arange(0, 360, 10).reshape((6, 6)), 
    index=[
        ['北京市', '北京市', '河北省', '河北省', '河南省', '河南省'],
        ['昌平区', '海淀区', '石家庄', '张家口', '驻马店', '平顶山'],
    ], 
    columns=[
        ['轻工业', '轻工业', '重工业', '重工业', '服务业', '服务业'],
        ['纺织业', '食品业', '冶金业', '采煤业', '教育业', '游戏业'],
    ]
)

data_frame1
data_frame1['重工业']
data_frame1.loc['河北省']
data_frame1.loc['河北省']['重工业']

处理缺失数据

缺失数据(missing data)在大部分数据分析应用中都很常见。补上缺失值非常用,它们在数据结构中用NaN来表示,便于识别.

处理缺失值一般有三种方式

  • 直接丢弃数据中的包含NaN的行或列.
from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame1 = DataFrame([
    [10, 20, 30, 40, 50],
    [11, 21, np.nan, 41, np.nan],
    [12, 22, 32, 42, 52],
    [np.nan, 66, np.nan, np.nan, np.nan],
    [np.nan, 24, np.nan, 44, 54],
])
data_frame1
# 默认丢弃包含NaN的行
data_frame1.dropna()
# 丢弃包含NaN的列
data_frame1.dropna(axis=1)
# 如果只是想删除所有元素是NaN的行或列, 需要参数how='all'
data_frame1.dropna(how='all', axis=1)
  • 在选取数据的时候过滤掉包含NaN的行或列.
from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame1 = DataFrame([
    [10, 20, 30, 40, 50],
    [11, 21, np.nan, 41, np.nan],
    [12, 22, 32, 42, 52],
    [np.nan, 66, np.nan, np.nan, np.nan],
    [np.nan, 24, np.nan, 44, 54],
])
data_frame1
# 获得第一列不为NaN的元素
data_frame1[0][data_frame1[0].notnull()]
0    10.0
1    11.0
2    12.0
Name: 0, dtype: float64
# 获得第三行不为NaN的元素
data_frame1.loc[3][~(data_frame1.loc[3].isnull())]
data_frame1.loc[3][data_frame1.loc[3].notnull()]
1    66.0
Name: 3, dtype: float64
  • 填充NaN值
from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame1 = DataFrame([
    [10, 20, 30, 40, 50],
    [11, 21, np.nan, 41, np.nan],
    [12, 22, 32, 42, 52],
    [np.nan, 66, np.nan, np.nan, np.nan],
    [np.nan, 24, np.nan, 44, 54],
])
data_frame1
data_frame1.fillna(666)
# 每一列替换为不同的值, 目前不支持对按行填充不同值的功能.
data_frame1.fillna({0: 100, 1: 200, 2:300, 3: 400, 4: 500})

数据准备

我们从文件或数据库等数据源获取数据之后,将数据转换为DataFrame格式后,我们就可以对数据进行处理了。数据处理的目的是准备数据,便于我们后续分析数据。

  • 我们下面将会深入学习pandas库在数据处理阶段的功能. 数据处理分为三个阶段:

1.数据准备
2.数据转换
3.数据聚合

  • 开始处理数据工作之前,需要先行准备好数据,把数据组装成便于用pandas库的各种工具处理的数据结构。数据准备阶段包括以下步骤:

1.数据加载 2. 组装:合并(merging)拼接(concatenation)组合(combine)3.变形(轴向旋转)

数据加载就是从文件、数据库等数据源中讲待处理的数据加载到程序中,并转换为DataFrame等结构。数据可能来自不同的数据源,有着不同的格式,需要将其归并为一个DataFrame, 然后才能做进一步的处理.

数据组装

对于存储在pandas对象中的各种数据,组装的方法有以下几种:

  • 合并--pandas.merge()函数根据一个或多个键连接多行.
  • 拼接--pandas.concat()函数按照轴把多个对象拼接起来.
  • 结合--pandas.DataFrame.combine_first()函数从另外一个数据结构获取数据,连接重合的数据,填充缺失值.

合并(merge)

对于合并操作,熟悉SQL的读者可以将其理解为JOIN操作,它使用一个或多个键把多行数据结合在一起.

Merge通过索引来合并数组

from pandas import DataFrame, Series
import pandas as pd
import numpy as np


data_frame1 = DataFrame({
    'name': ['John', 'Edward', 'Smith', 'Obama', 'Clinton' ],
    'ages1': [18, 16, 15, 14, 19]
})

data_frame2 = DataFrame({
    'name': ['John', 'Edward', 'Polly', 'Obama' ],
    'ages2': [19, 16, 15, 14]
})

data_frame3 = DataFrame({
    'other_name': ['John', 'Edward', 'Smith', 'Obama', 'Bush' ],
    'other_ages': [18, 16, 15, 14, 20]
})
print(data_frame1)
print('-'*10)
print(data_frame2)
print('-'*10)
print(data_frame3)
      name  ages1
0     John     18
1   Edward     16
2    Smith     15
3    Obama     14
4  Clinton     19
----------
     name  ages2
0    John     19
1  Edward     16
2   Polly     15
3   Obama     14
----------
  other_name  other_ages
0       John          18
1     Edward          16
2      Smith          15
3      Obama          14
4       Bush          20
# 默认根据相同列名作为键连接两个DataFrame, 并且内连接方式(inner, 取两边都有的数据, 交集)
# 那么请问, 当我将data_frame2的列名ages2改为ages1时,结果是多少?
pd.merge(data_frame1, data_frame2, on='name')
# 可修改连接方式为外连接(outer),取两遍都有数组, 缺失数据使用NaN表示
pd.merge(data_frame1, data_frame2, how='outer')
# 当两个DataFrame没有公共相同的列名的时候,那么合并就会报错.
# MergeError: No common columns to perform merge on
# 此时我们可指定DataFrame用那一列名来连接另一个DataFrame的那个列名
# 使用参数 left_on 和 right_on
pd.merge(data_frame1, data_frame3, left_on='name', right_on='other_name')
# 还可以在此基础上使用外连接
pd.merge(data_frame1, data_frame3, left_on='name', right_on='other_name', how='outer')
# 除了内外连接,还有左连接(left)和右连接(right)
# 左连接以左DataFrame为准, 右DataFrame不存在的列用NaN代替.
pd.merge(data_frame1, data_frame2, how='left')
# 左连接以右DataFrame为准, 左DataFrame不存在的列用NaN代替.
pd.merge(data_frame1, data_frame2, how='right')
# 有时候,DataFrame中连接的键在索引(index)中。
# 在这种情况下传入left_index=True, right_index=True
# 说明索引被应用于连接键.
pd.merge(data_frame1, data_frame2, left_index=True, right_index=True, suffixes=['_a', '_b'])

拼接(concat)

concat函数可以将DataFrame、Series根据不同的轴作简单的融合

pd.concat(objs, axis=0, join='outer')

参数说明:objs: series,dataframe构成的序列lsit axis: 需要合并链接的轴,0是行,1是列 join:连接的方式 inner,或者outer

from pandas import DataFrame, Series
import pandas as pd

df1 = DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
                    'B': ['B0', 'B1', 'B2', 'B3'],
                    'C': ['C0', 'C1', 'C2', 'C3'],
                    'D': ['D0', 'D1', 'D2', 'D3']},
                    index=[0, 1, 2, 3])

df2 = DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
                    'B': ['B4', 'B5', 'B6', 'B7'],
                    'C': ['C4', 'C5', 'C6', 'C7'],
                    'D': ['D4', 'D5', 'D6', 'D7']},
                     index=[4, 5, 6, 7])

df3 = DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
                    'B': ['B8', 'B9', 'B10', 'B11'],
                    'C': ['C8', 'C9', 'C10', 'C11'],
                    'D': ['D8', 'D9', 'D10', 'D11']},
                    index=[8, 9, 10, 11])

df4 = pd.DataFrame({'B': ['B2', 'B3', 'B6', 'B7'],
                    'D': ['D2', 'D3', 'D6', 'D7'],
                    'F': ['F2', 'F3', 'F6', 'F7']},
                    index=[2, 3, 6, 7])

result = pd.concat([df1, df2, df3])
# 默认是将后者DataFrame向纵方向拼接.
result
# 上面拼接完成之后,我们不清楚在Result中哪些数据是data_frame1
# 哪些数据是data_frame1, 我们可以通过keys为其增加等级索引
result = pd.concat([df1, df2, df3], keys=['x', 'y', 'z'])
result

下面我们看看如何横向拼接两个DataFrame:

  • 横向拼接,我们只需要将设置axis=1.
result = pd.concat([df1, df4], axis=1)
result

我们下面看看join如何用, join有两个可选项, 默认是outer(两个DataFrame并集), 也可设置为inner(两个DataFrame交集).

result = pd.concat([df1, df4], axis=1, join='inner')
result

组合(combine)

假如我们有索引全部或部分重叠的两个数据集. 我们可能希望用一个数据集中的元素去补充另外一个数据集中的元素.例如:

from pandas import DataFrame, Series
import pandas as pd
import numpy as np

series1 = Series([np.nan, 10, 20, np.nan, 30], index=list('abcde'))
series2 = Series([100, 11, 22, 200, np.nan], index=list('abcde'))

series1

#    a     NaN
#    b    10.0
#    c    20.0
#    d     NaN
#    e    30.0
#    dtype: float64

series2

#    a    100.0
#    b     11.0
#    c     22.0
#    d    200.0
#    e      NaN
#    dtype: float64

# 如果series1中的元素为NaN,那么用series2中的元素来补充
# 如果series2中的元素在series1中不存在, 则在series1中新增元素
series1.combine_first(series2)

#    a    100.0
#    b     10.0
#    c     20.0
#    d    200.0
#    e     30.0
#    dtype: float64
a    100.0
b     10.0
c     20.0
d    200.0
e     30.0
dtype: float64

轴向旋转

我们除了要将来自不同数据源的进行整合统一之外,另外一种常用操作成为轴向旋转(pivoting). 轴向旋转操作我们主要用到两个函数:

1.入栈(stacking):旋转数据结构,把列转换为行.
2.出栈(unstacking): 把行转换为列

from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame = DataFrame(
    np.arange(9).reshape((3, 3)), 
    index=list('abc'), 
    columns=list('efg')
)

data_frame
# 将data_frame的列['e', 'f', 'g']转换为行
data_frame.stack()
a  e    0
   f    1
   g    2
b  e    3
   f    4
   g    5
c  e    6
   f    7
   g    8
dtype: int64
# 将行转换为列
data_frame.stack().unstack()
# 将行转换为列 
# 0表示将['a', 'b', 'c']转换为列
# 1表示将['e', 'f', 'g']转换为列
data_frame.stack().unstack(0)

数据转换

我们已经学习了如何准备数据以便于进行数据分析. 这个过程体现在重组DataFrame中的数据上.现在我们进行第二步数据转换.

在数据转换过程中,有些操作会涉及重复或无效元素,可能需要将其删除或者替换为别的元素.

删除重复行

数据处理的最后一步是删除多余的列和行.前面我们已经学习了del、DataFrame.drop()删除行列.由于多种原因,DataFrame对象可能包含重复的行.在大型的DataFrame中,检测重复的行可能遇到各种问题。pandas为此提供了多种工具,便于分析大型数据中的重复数据.

这里我们主要学习连个用于检测和删除重复行的函数

1.duplicated函数,用于检测重复行,返回元素为Bool类型的Series对象. 2.drop_duplicates, 用于从DataFrame对象中删除重复的行.

from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame = DataFrame({
    'name': ['John', 'John', 'Edward', 'Smith', 'Edward', 'John'],
    'age': [19, 19, 20, 21, 20, 19]
})

data_frame
# 检测是否存在重复行
data_frame.duplicated()
0    False
1     True
2    False
3    False
4     True
5     True
dtype: bool
# 删除重复行
data_frame.drop_duplicates()

映射相关

pandas提供了几个利用映射关系来实现某些操作的函数。映射关系无非就是创建一个映射关系的列表,把元素跟一个特定的标签或者字符串绑定起来.

我们主要学习以下三个映射函数:1.replace(): 替换元素 2. rename(): 替换索引 3.map(): 新建一列.

替换元素(replace)

组装完数据结构后,里面通常会有些元素不符合需求. 需要将某些文本替换为另外的文本,例如存在外语文本.

from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame1 = DataFrame({
    'name': ['xxx', 'John', 'Edward', 'Smith', 'xxx', 'yyy', np.nan],
    'age': np.arange(10, 17),
})

data_frame1
# 将DataFrame中的xxx替换为Obama, yyy替换成Polly
replace_map = {'xxx': 'Obama', 'yyy': 'Polly'}
data_frame1.replace(replace_map, inplace=True)
data_frame1
# 将DataFrame中的NaN替换成zzz
data_frame1.replace(np.nan, 'zzz')

重命名轴索引(rename)

pandas的rename函数可以用来替换轴的索引标签.

from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame = DataFrame(np.arange(12).reshape((4, 3)))
data_frame
re_index = {
    0: 'a',
    1: 'b',
    2: 'c',
    3: 'd'
}

re_column = {
    0: 'fir',
    1: 'sec',
    2: 'thi'
}

# 修改索引标签
# 也可以指定哪些标签需要修改,不需要每次都指定
data_frame.rename(index=re_index, columns=re_column, inplace=True)
data_frame

使用映射添加元素(map)

from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame = DataFrame({
    'name': ['John', 'Edward', 'Smith', 'Obama', 'Polly'],
    'age': np.arange(18, 23),
})

data_frame
# 我们现在有一个字典存储了学生对应的学习成绩
score = {
    'John': 89,
    'Edward': 98,
    'Smith': 85,
    'Bush': '83',
    'Obama': 78,
    'Polly': 67,
    'Donald': 77
}

# 现在我们想在data_frame中增加一列分数
data_frame['score'] = data_frame['name'].map(score)
data_frame

分组(GroupBy)

Hanley Wickham(许多R语言包的作者),创造了一个用于表示分组运算的术语'split-apply-combine'(拆分-应用-合并), 这个词很好描述了整个过程。 分组运算的第一个阶段, pandas对象(Series和DataFrame)中的数据会根据你所提供的一个或多个键被拆分(split)成多组. 拆分操作是在对象的特定轴上进行的. 例如,DataFrame可以在其行(axis=0)或列(axis=1)上进行分组. 然后, 讲一个函数应用(apply)到各个分组并产生一个新值. 最后,所有这些函数的执行结果会被合并(combine)到最终的结果对象中.

  1. 根据某一列或多列分组
from pandas import DataFrame, Series
import pandas as pd
import numpy as np

data_frame = DataFrame({
    'key1': ['a', 'a', 'b', 'b', 'a'],
    'key2': ['one', 'two', 'one', 'two', 'three'],
    'data1': np.arange(5),
    'data2': np.arange(10, 15)
})

data_frame
# 按照key1对数据进行分组
data_gruop_by_key1 = data_frame.groupby('key1')

# 输出a、b两组数据
dict([group for group in data_gruop_by_key1])['a']
dict([group for group in data_gruop_by_key1])['b']
# 计算每一组的平均值
print(data_gruop_by_key1.mean())
print('-'*30)

# 每一组元素个数
print(data_gruop_by_key1.size())
print('-'*30)
         data1      data2
key1                     
a     1.666667  11.666667
b     2.500000  12.500000
------------------------------
key1
a    3
b    2
dtype: int64
------------------------------
# 对某一列数据进行分组
data1_gruop_by_key1 = data_frame['data1'].groupby(data_frame['key1'])
print(data1_gruop_by_key1.mean())
key1
a    1.666667
b    2.500000
Name: data1, dtype: float64
data_frame = DataFrame({
    'key1': ['a', 'a', 'b', 'b', 'a'],
    'key2': ['one', 'one', 'one', 'two', 'three'],
    'data1': np.arange(5),
    'data2': np.arange(10, 15)
})

data_frame
# 按照key1、key2进行分组
data_gruop_by_key12 = data_frame.groupby(['key1', 'key2'])
data_gruop_by_key12.sum()
  • 根据索引值分组
data_frame = DataFrame(
    np.random.randint(10, 13, (6, 6)),
    index=list('aabbcc'),
    columns=list('112233')
)

data_frame
# 根据列索引分组, 并求每一组平均值
data_frame.groupby(level=0, axis=1).mean()
# 根据行索引分组, 并求每一组平均值
data_frame.groupby(level=0, axis=0).mean()
  • 手动指定索引进行分组
data_frame = DataFrame(
    np.arange(36).reshape((6, 6)), 
    columns=list('abcdef')
)

data_frame
# 按照列进行分组
# 也就是手动指定哪些索引为一组
group_mapping = {
    'a': 'first',
    'b': 'first',
    'c': 'first',
    'd': 'second',
    'e': 'third',
    'f': 'third',
}

data_group_by_dict = data_frame.groupby(group_mapping, axis=1)

# 输出三组信息
print(dict([x for x in data_group_by_dict])['first'])
print('-'*13)
print(dict([x for x in data_group_by_dict])['second'])
print('-'*13)
print(dict([x for x in data_group_by_dict])['third'])
    a   b   c
0   0   1   2
1   6   7   8
2  12  13  14
3  18  19  20
4  24  25  26
5  30  31  32
-------------
    d
0   3
1   9
2  15
3  21
4  27
5  33
-------------
    e   f
0   4   5
1  10  11
2  16  17
3  22  23
4  28  29
5  34  35
# 对每一组进行求和计算
print(data_group_by_dict.sum())
   first  second  third
0      3       3      9
1     21       9     21
2     39      15     33
3     57      21     45
4     75      27     57
5     93      33     69
# 将行索引分组
data_frame = DataFrame(
    np.arange(36).reshape((6, 6)), 
    index=list('abcdef')
)

print(data_frame)
    0   1   2   3   4   5
a   0   1   2   3   4   5
b   6   7   8   9  10  11
c  12  13  14  15  16  17
d  18  19  20  21  22  23
e  24  25  26  27  28  29
f  30  31  32  33  34  35
group_mapping = {
    'a': 'first',
    'b': 'first',
    'c': 'first',
    'd': 'second',
    'e': 'third',
    'f': 'third',
}

data_group_by_dict = data_frame.groupby(group_mapping, axis=0)

# 输出三组信息
print(dict([x for x in data_group_by_dict])['first'])
print('-'*25)
print(dict([x for x in data_group_by_dict])['second'])
print('-'*25)
print(dict([x for x in data_group_by_dict])['third'])

data_group_by_dict.sum()
    0   1   2   3   4   5
a   0   1   2   3   4   5
b   6   7   8   9  10  11
c  12  13  14  15  16  17
-------------------------
    0   1   2   3   4   5
d  18  19  20  21  22  23
-------------------------
    0   1   2   3   4   5
e  24  25  26  27  28  29
f  30  31  32  33  34  35

pandas的I/O操作

我们学习了pandas库以及它所提供的用于数据分析的基础功能,了解了DataFrame和Series是这个库的核心,数据处理、计算和分析都是围绕它们展开.

Pandas是数据分析专用库,它所关注的是数据计算和处理。从外部文件读写数据也是数据处理的重要部分,于是pandas提供了用于文件数据读写的专门的工具, 这些工具提供了将不同的数据结构写入不同格式文件的方法,而无需过多考虑所使用的技术.

这些用于读写数据文件的函数分为对称的两大类:读取函数和写入函数.


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容