《Python数据科学手册》抄书笔记,第三章: Pandas数据处理

Pandas是在NumPy基础上建立的新程序库,提供了一种高效的DataFrame数据结构。DataFrame本质上是一种带行标签和列标签、支持相同类型数据和缺失值的多维数组。

3.1 安装并使用pandas

import pandas

pandas.__version__

'1.0.5'

 一般会简写成pd

import pandas as pd

3.2Pandas对象介绍

如果从底层视角观察Pandas对象,可以把它们看成增强版的NumPy结构化数组,行列都不再只是简单的整数索引,还可以带上便签。

Pandas的三个基本数据结构:Series, DateFrame,Index

先从倒包开始:

import numpy as np
import pandas as pd 

3.2.1 Pandas的series对象

Pandas的Series对象是一个带索引数据构成的一维数组。可以用一个数组创建Series对象,如下所示:

data = pd.Series(np.linspace(0.25,1,4))

data

0    0.25
1    0.50
2    0.75
3    1.00
dtype: float64

 从输出可以看到Series对象将一组数据和一组索引绑定在一起,可以从对象的values和index获取相关属性。

data.values

array([0.25, 0.5 , 0.75, 1.  ])
data.index

RangeIndex(start=0, stop=4, step=1)

和Numpy'数组一样,数据可以通过Python的中括号索引标签获取:

data[1]

0.5

data[1:3]

1    0.50
2    0.75
dtype: float64

1.Series是通用的Numpy数组

你可能会觉得Series对象和一维NumPy数组基本可以等价交换,但两者的本质差异其实是索引:

NumPy数组通过隐式定义的整数获取索引数值,而Pandas对象用一种显式定义的索引与数值关联。

注意一个隐式,一个是显式

显式索引让Series的索引不仅仅是整数,还可以是任意想要的类型。

data = pd.Series([0.25,0.5,0.75,1.0],index=list('abcd'))

data

a    0.25
b    0.50
c    0.75
d    1.00
dtype: float64

 取值还是通过索引取值

data['b']

0.5

 也可以使用不连续索引:

data = pd.Series([0.25,0.5,0.75,1.0],index=[2,5,3,7])

data

2    0.25
5    0.50
3    0.75
7    1.00
dtype: float64

data[5]

0.5

2.Series是特殊的字典

我们可以把Pandas的Series对象看成一种特殊的Python字典。字典是这一种将任意键映射到一组任意值的数据结构,而Series对象其实是一种将类型键映射到一组类型值的数据结构。可以直接用Python的字典创建一个Series对象。、

population_dict = {'California': 38332521,
                   'Texas': 26448193,
                   'New York': 19651127,
                   'Florida': 19552860,
                   'Illinois': 12882135}
population = pd.Series(population_dict)
population

 输出

California    38332521
Texas         26448193
New York      19651127
Florida       19552860
Illinois      12882135
dtype: int64

 用字典创建Series对象时,其索引默认按照顺序排序。取值还是通过索引取值

population['California']

38332521

 与字典不同,Series还支持切片操作

population['California':'Illinois']

California    38332521
Texas         26448193
New York      19651127
Florida       19552860
Illinois      12882135
dtype: int64

创建Series对象

前面已经看到过的创建方式:

pd.Series(data, index=index)

 index是一个可选参数,data参数支持多种数据类型。

pd.Series([2, 4, 6])

0    2
1    4
2    6
dtype: int64

 上面演示了默认的index,data也可以是标量,索引后面会自动填充

pd.Series(5, index=[100,200,300])

100    5
200    5
300    5
dtype: int64

 data还可以是一个字典,index默认是排序的字典key

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

2    a
1    b
3    c
dtype: object

 每一种形式可以通过显式指定索引需要的结果:

pd.Series({2:'a',1:'b',3:'c'},index=[3,2])

3    c
2    a
dtype: object

 筛选出来的将不在排序,而且需要注意的是,Series对象只会保留显式定义的键值对。

3.2.2Pandas的DataFrame对象

同样DataFrame既可以作为一个通用型NumPy数组,也可以看做特殊的Python字典。

先再来创建另外一个Series对象:

area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297,
             'Florida': 170312, 'Illinois': 149995}
area = pd.Series(area_dict)
area
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
dtype: int64

 与之前创建population的Series对象一样,用一个字典创建这些二维对象。

states = pd.DataFrame({'populatioan':population,
                      'area': area})
states

 输出

 	populatioan 	area
California 	38332521 	423967
Texas 	26448193 	695662
New York 	19651127 	141297
Florida 	19552860 	170312
Illinois 	12882135 	149995

 DataFrame也有index属性

states.index

Index(['California', 'Texas', 'New York', 'Florida', 'Illinois'], dtype='object')

 DataFrame还有一个columns属性,是存放标签的index对象

states.columns

Index(['populatioan', 'area'], dtype='object')

 因此DateFrame可以看作一种通用的NumPy二维数组,它的行与列都可以通过索引获取

states['area']
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64

 通过索引可以获取一个列数据,也就是一个Series

3.创建DataFrame对象

1通过Series对象创建。DataFrame是一组Series对象的集合,可以用单个Series创建一个单列的DateFrame

pd.DataFrame(population,columns=['population'])

 输出

 	population
California 	38332521
Texas 	26448193
New York 	19651127
Florida 	19552860
Illinois 	12882135

 2通过字典的列表创建。任何只要是列表套字典的形式就可以转换成DataFrame,字典的key是DateFrame的columns名称。

data =[{'a':i,'b':2*i} 
      for i in range(3)]
pd.DataFrame(data)

 输出

a 	b
0 	0 	0
1 	1 	2
2 	2 	4

 即使有些键不存在,Pandas也会用缺失值Nan

pd.DataFrame([{'a': 1, 'b': 2}, {'b': 3, 'c': 4}])

 输出

a 	b 	c
0 	1.0 	2 	NaN
1 	NaN 	3 	4.0

通过Series对象字典创建,跟前面的示例一样

pd.DataFrame({'polulation':population,
             'area': area})

 输出

polulation 	area
California 	38332521 	423967
Texas 	26448193 	695662
New York 	19651127 	141297
Florida 	19552860 	170312
Illinois 	12882135 	149995

通过二维数组创建,如果如果不指定行与列的索引,那么行列默认都是整数索引值

pd.DataFrame(np.random.rand(3,2),
            columns=['foo','bar'],
            index=['a','b','c'])

 输出

foo 	bar
a 	0.926270 	0.753726
b 	0.537491 	0.967508
c 	0.817875 	0.590719

最后书中的说明,是将结构化数组直接转换

A=np.zeros(3,dtype=({'names':('A','B'),'formats':('i8','f8')}))

A

array([(0, 0.), (0, 0.), (0, 0.)], dtype=[('A', '<i8'), ('B', '<f8')])

pd.DataFrame(A)

	A 	B
0 	0 	0.0
1 	0 	0.0
2 	0 	0.0

3.2.3Pandas的Index对象

Pandas的index是一个不可变的数组,同时也是一个有序的集合。

首先创建一个Index对象:

ind = pd.Index([2,3,5,7,11])

ind

Int64Index([2, 3, 5, 7, 11], dtype='int64')

1将index看做不可变的数组

尽然这么说了,除了赋值,另外的操作应该都可以

ind[1]

3

ind[::2]

Int64Index([2, 5, 11], dtype='int64')

Index objects also have many of the attributes familiar from NumPy arrays:

print(ind.size, ind.shape, ind.ndim, ind.dtype)

5 (5,) 1 int64

 可以切片,可以通过索引取值,可以输出size,shape等属性。

但通过索引赋值就报错。

Index对象的不可变特性,使得多个DateFrame和数组之间进行索引共享时更加安全,尤其是可以避免修改索引时粗心大意而导致的副作用。

将Index看作有序集合

indA = pd.Index([1, 3, 5, 7, 9])

indB = pd.Index([2, 3, 5, 7, 11])

indA & indB  # intersection

Int64Index([3, 5, 7], dtype='int64')

indA | indB  # union

Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64')

indA ^ indB  # symmetric difference

Int64Index([1, 2, 9, 11], dtype='int64')

 与Python的集合操作一样,分别操作了并集,交集,异或

































3.3数据取值与选择

3.3.1Series数据选择的方法

把Series对象与NumPy数组和Python字典在许多方面一样,记住这个类比,可以让我们更好的理解Series对象的数据索引与选择模式

1.将Series看作字典

 
import pandas as pd
import numpy as np
data = pd.Series(np.linspace(0.25,1,4),
                index=list('abcd'))
data
a    0.25
b    0.50
c    0.75
d    1.00
dtype: float64
 
data['b']
0.5

还可以用Python字典的一些方法

 
 
'a' in data
True
 
 
data.keys()
Index(['a', 'b', 'c', 'd'], dtype='object')
 
list(data.items())
[('area',
  California    423967
  Texas         695662
  New York      141297
  Florida       170312
  Illinois      149995
  Name: area, dtype: int64),
 ('pop',
  California    38332521
  Texas         26448193
  New York      19651127
  Florida       19552860
  Illinois      12882135
  Name: pop, dtype: int64)]

通过添加索引值,理解为字典添加key,来扩展Series

 
 
data['e'] = 1.25
 
 
data
 areapope
California 423967 38332521 1.25
Texas 695662 26448193 1.25
New York 141297 19651127 1.25
Florida 170312 19552860 1.25
Illinois 149995 12882135 1.25

2将Series看作一维数组

Series不仅有着和字典一样的接口,而且还具备和NumPy数组一样的数组数据选择功能,包括索引、掩码、花哨的索引等操作

 
 
data
 areapope
California 423967 38332521 1.25
Texas 695662 26448193 1.25
New York 141297 19651127 1.25
Florida 170312 19552860 1.25
Illinois 149995 12882135 1.25
 
 
# 将显式索引作为切片
data['a':'c']
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/indexes/base.py in get_slice_bound(self, label, side, kind)
   4844             try:
-> 4845                 return self._searchsorted_monotonic(label, side)
   4846             except ValueError:

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/indexes/base.py in _searchsorted_monotonic(self, label, side)
   4805 
-> 4806         raise ValueError("index must be monotonic increasing or decreasing")
   4807 

ValueError: index must be monotonic increasing or decreasing

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-110-eccbdf2cf843> in <module>
      1 # 将显式索引作为切片
----> 2 data['a':'c']

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/frame.py in __getitem__(self, key)
   2777 
   2778         # Do we have a slicer (on rows)?
-> 2779         indexer = convert_to_index_sliceable(self, key)
   2780         if indexer is not None:
   2781             # either we have a slice or we have a string that can be converted

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/indexing.py in convert_to_index_sliceable(obj, key)
   2265     idx = obj.index
   2266     if isinstance(key, slice):
-> 2267         return idx._convert_slice_indexer(key, kind="getitem")
   2268 
   2269     elif isinstance(key, str):

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/indexes/base.py in _convert_slice_indexer(self, key, kind)
   2961             indexer = key
   2962         else:
-> 2963             indexer = self.slice_indexer(start, stop, step, kind=kind)
   2964 
   2965         return indexer

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/indexes/base.py in slice_indexer(self, start, end, step, kind)
   4711         slice(1, 3)
   4712         """
-> 4713         start_slice, end_slice = self.slice_locs(start, end, step=step, kind=kind)
   4714 
   4715         # return a slice

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/indexes/base.py in slice_locs(self, start, end, step, kind)
   4924         start_slice = None
   4925         if start is not None:
-> 4926             start_slice = self.get_slice_bound(start, "left", kind)
   4927         if start_slice is None:
   4928             start_slice = 0

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/indexes/base.py in get_slice_bound(self, label, side, kind)
   4846             except ValueError:
   4847                 # raise the original KeyError
-> 4848                 raise err
   4849 
   4850         if isinstance(slc, np.ndarray):

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/indexes/base.py in get_slice_bound(self, label, side, kind)
   4840         # we need to look up the label
   4841         try:
-> 4842             slc = self.get_loc(label)
   4843         except KeyError as err:
   4844             try:

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
   2646                 return self._engine.get_loc(key)
   2647             except KeyError:
-> 2648                 return self._engine.get_loc(self._maybe_cast_indexer(key))
   2649         indexer = self.get_indexer([key], method=method, tolerance=tolerance)
   2650         if indexer.ndim > 1 or indexer.size > 1:

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'a'

 
 
# 将数字整数索引作为切片
data[0:2]
 
# 掩码取值
data[(data>0.3)&(data<0.8)]
 
 
# 花哨取值
data[['a','e']]

切片索引记住,用显式的时候时取头取尾的,用隐式的时候时取头不取尾的

3.索引器:loc、iloc和ix

如果是显式的整数索引,就会与隐式的让你迷惑

 
data = pd.Series(list('abc'),
                 index=[1,3,5])
data
 
 
# 索引取值是显式的
data[1]
 
# 切片用的是隐式的
data[1:3]

由于整数索引的混淆,Pandas提供了索引器(indexer)属性来取值的方法。它们不是Series对象的函数方法,而是暴露切片接口的属性

 
 
# loc显式索引的使用
data.loc[1]
 
 
data.loc[1:3]
 
 
# iloc隐式索引的使用
 
 
data.iloc[1]
 
 
data.iloc[1:3]
 
pd.__version__
 
 
pd.ix

在Pandas1.0.5,Series对象已经没有ix属性了

3.3.2DataFrame数据选择方法

DateFrame有些方面像二维或结构化数组,在有些方面又像共享索引的若干Series对象构成的字典

将DataFrame看作字典

先创建两个Series对象

 
area = pd.Series({'California': 423967, 'Texas': 695662,
                  'New York': 141297, 'Florida': 170312,
                  'Illinois': 149995})
pop = pd.Series({'California': 38332521, 'Texas': 26448193,
                 'New York': 19651127, 'Florida': 19552860,
                 'Illinois': 12882135})
 
# 通过Series创建DataFrame
data = pd.DataFrame({'area':area,'pop':pop})
data
 areapop
California 423967 38332521
Texas 695662 26448193
New York 141297 19651127
Florida 170312 19552860
Illinois 149995 12882135

获取列的数据可以通过字典方式[key]也可以通过.attr的方式获取

 
data['area']
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64
 
data.area
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64
 
data['area'] is data.area
True

可以看到取出的对象是相同的

如果列名不是存字符形式,或者与DateFrame的方法名重名就不行

 
data.pop is data['pop']
False
 
data.pop
<bound method NDFrame.pop of               area       pop
California  423967  38332521
Texas       695662  26448193
New York    141297  19651127
Florida     170312  19552860
Illinois    149995  12882135>
 
data['pop']
California    38332521
Texas         26448193
New York      19651127
Florida       19552860
Illinois      12882135
Name: pop, dtype: int64

和前面的Series添加索引类似,DateFrame添加columns也非常方便,通过增加key的方式就可以了

 
data['density'] = data['pop'] / data.area
data
 areapopdensity
California 423967 38332521 90.413926
Texas 695662 26448193 38.018740
New York 141297 19651127 139.076746
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763

2.将DateFrame看作二维数组

 
data
 areapopdensity
California 423967 38332521 90.413926
Texas 695662 26448193 38.018740
New York 141297 19651127 139.076746
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
 
# DataFrame的values属性查看数据
data.values
array([[4.23967000e+05, 3.83325210e+07, 9.04139261e+01],
       [6.95662000e+05, 2.64481930e+07, 3.80187404e+01],
       [1.41297000e+05, 1.96511270e+07, 1.39076746e+02],
       [1.70312000e+05, 1.95528600e+07, 1.14806121e+02],
       [1.49995000e+05, 1.28821350e+07, 8.58837628e+01]])
 
# 进行转置
data.T
 CaliforniaTexasNew YorkFloridaIllinois
area 4.239670e+05 6.956620e+05 1.412970e+05 1.703120e+05 1.499950e+05
pop 3.833252e+07 2.644819e+07 1.965113e+07 1.955286e+07 1.288214e+07
density 9.041393e+01 3.801874e+01 1.390767e+02 1.148061e+02 8.588376e+01
 
# 获取行信息,通过values属性来
data.values[0]
array([4.23967000e+05, 3.83325210e+07, 9.04139261e+01])
 
# 获取列信息,直接通过列索引就可以了
data.area
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64

当然也可以通过loc【显式】或iloc【隐式】,进行行与列的选择

 
# 用隐式的方式取三行两列
data.iloc[:3, :2]
 areapop
California 423967 38332521
Texas 695662 26448193
New York 141297 19651127
 
# 用显式的方式取三行两列,记住这个是取头取尾的
data.loc[:'New York', :'pop']
 areapop
California 423967 38332521
Texas 695662 26448193
New York 141297 19651127

任何用于处理NumPy形式数据的方法都可以用于这些索引器

 
# 结合掩码与花哨索引取值
data.loc[data.density>100, ['pop','density']]
 popdensity
New York 19651127 139.076746
Florida 19552860 114.806121
 
# 赋值操作
data.iloc[0,2] =90
data
 areapopdensity
California 423967 38332521 90.000000
Texas 695662 26448193 38.018740
New York 141297 19651127 139.076746
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763

如果对单个标签取值就是选择列,而对多个便签进行切片就选择行

 
data['Florida':'Illinois']
 areapopdensity
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
 
# 直接通过数字索引取值
data[-2:]
 areapopdensity
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
 
# 直接通过列数据的筛选,不是要是loc索引器
data[data.density >100]
 areapopdensity
New York 141297 19651127 139.076746
Florida 170312 19552860 114.806121
 
data.loc[data.density > 100]
 areapopdensity
New York 141297 19651127 139.076746
Florida 170312 19552860 114.806121
 
n = data.density > 100
n
California    False
Texas         False
New York       True
Florida        True
Illinois      False
Name: density, dtype: bool


# 3.5处理缺省值

## 3.5.1选择处理缺失值的方法

一般分为两种:一种方法是通过一个覆盖全局的掩码表示缺失值,另一种方法是用一个便签值(sentinel value)表示缺失值

## 3.5.2Pandas的缺失值

Pandas选择了标签方法表示缺失值,包括两种Python原有的缺失值:浮点数据类型的NaN值,以及Python的None对象

### 1.None:Python对象类型的缺失值

None在ndarray中属于object对象

import numpy as np
import pandas as pd

vals1 = np.array([1, None, 3, 4])
vals1

这里判断ndarray的dtype为object,,这种数据比NumPy的其他原生数据要消耗更多的资源

for dtype in ['object', 'int']:
    print('dtype =', dtype)
    %timeit np.arange(1E6)

由于元素为Python对象,所以一些方法如sum,min将无法使用

vals1.sum()

### 2.NaN:数值类型的缺失值

另一种缺失值的标签是NaN(全称Not a Number,不是一个数字),是一种按照IEEE浮点数标准设计、在任何系统中都兼容的特殊浮点数。

vals2 = np.array([1, np.nan, 3, 4])
vals2.dtype

任何与NaN操作过数字都会变成NaN

1 + np.nan

0 / np.nan

0 > np.nan

0 <= np.nan

所以通过普通的求和,最大值,最小值会出现问题

vals2.sum(), vals2.min(), vals2.max()

np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)

记住NaN是一种特殊的浮点数。

### 3.Pandas中NaN与None的差异

在Pandas里面NaN与None可以等价交换

pd.Series([1, np.nan, 2, None])

Pandas自动将没有便签的值转化为NaN

增加索引赋值为None的时候,也会自动转换NaN,且会更加对象的类型

x = pd.Series(range(2), dtype=int)
x

x[0] = None
x

pd.Series(['add'])

### 3.5.3处理缺失值

Pandas内置了一些来发现、剔除、替换数据结构种的缺失值

### 1.发现缺失值

isnull()和notnull(),每种方法都返回布尔类型的掩码数据

data = pd.Series([1, np.nan, 'hello', None])
data.isnull()

# 掩码取值
data[data.notnull()]

isnull与notnull同样适用与DataFrame

## 2.剔除缺失值 

dropna()剔除缺省值,fillna()填充缺省值

data

data.dropna()

df = pd.DataFrame([[1, np.nan,2],
                  [2, 3,5],
                  [None,4,6]])
df

df.dropna()

默认会删除行中有NaN数据的行

df.dropna(axis='columns')

df.dropna(axis=1)

但有时候并想只有一个NaN就删除整行或者整列,那就需要用到参数how或者thresh

df[3] = np.nan
df

# 删除只有全部是NaN的列,默认的how就是any
df.dropna(axis='columns', how='all')

通过thresh参数设定非缺失值的最小数量,注意是非缺失值的数量。

df.dropna(axis='rows', thresh=3)

df.iloc[0] = None
df

### 填充缺失值

通过fillna()方法,返回一个填充了缺失值后的数组副本

data = pd.Series([1, np.nan,2,None,3], index=list('abcde'))
data

# 填充一个固定的值
data.fillna(0)

# 从前往后填充 forward-fill
data.fillna(method='ffill')

# 从后往前填充 back-fill
data.fillna(method='bfill')

操作DataFrame相同,只不过需要指定axis

df

df.fillna(method='ffill', axis=1)

从前往后填充,第一个是NaN,无法填充

# 3.6层级索引

通过层级索引(hierarchical indexing, 也被称为多级索引,multi-indexing)配合多个有不同等级(level)的一级索引一起使用,这样就可以将高维度数组转换成类似一维Series和二维DataFrame对象的形式

import numpy as np
import pandas as pd

## 3.6.1多级索引Series

### 笨方法

# 通过索引给一个元祖,元祖里面有两个索引的元素
index = [('California', 2000), ('California', 2010),
         ('New York', 2000), ('New York', 2010),
         ('Texas', 2000), ('Texas', 2010)]
populations = [33871648, 37253956,
               18976457, 19378102,
               20851820, 25145561]
pop = pd.Series(populations, index=index)
pop

# 通过切片取值
pop[('California', 2010):('Texas', 2000)]

# 需要取特定的元素,比如2010年的就比较麻烦,且效率低
pop[[i for i in pop.index if i[1] == 2010]]

### 好方法:Pandas多级索引

# 这个显式跟书中的已经不一样,个人感觉还是这样的不错
index = pd.MultiIndex.from_tuples(index)
index

# 更换索引
pop = pop.reindex(index)
pop

# 索引直接可以通过切片获取
pop[:, 2010]

### 3.高维数据的多级索引

多级索引可以转换成一个带行列索引的简单DataFrame。unstack()方法可以快速将一个多级索引的Series转换为普通的DataFrame

pop

pop_df=pop.unstack()
pop_df

# 当然也可以通过stack的方法转换为Series
pop_df.stack()

我们可以用含多级索引的一维Series数据表示二维数据,那么我们就可以用Series或DataFrame表示三维甚至更高维度的数据。
多级索引每增加一级,就表示数据增加一维,利用这一特点就可以表示任意长度的数据了。

# 增加一列数据,变成DataFrame
pop_df = pd.DataFrame({'total':pop,
                      'under18': [9267089, 9284094,
                                   4687374, 4318033,
                                   5906301, 6879014]})
pop_df

增加一些我自己的理解,上面的DataFrame其实在ndarray里面可以理解为三维,因为进来的Series是多重索引的,可以理解为二维,增加了一个colums维度

f_u18 = pop_df['under18'] / pop_df['total']
f_u18

# 转换为DataFrame
f_u18.unstack()

### 3.6.2多级索引的创建方法

为Series或DataFrame创建多级索引最直接的办法是将index参数设置为至少二维的索引数组

df = pd.DataFrame(np.random.rand(4, 2),
                  index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]], # 二维的数组
                  columns=['data1', 'data2'])
df

如果把元祖作为键的字典传给Pandas,它默认也会把index处理为MutilIndex

data = {('California', 2000): 33871648,
        ('California', 2010): 37253956,
        ('Texas', 2000): 20851820,
        ('Texas', 2010): 25145561,
        ('New York', 2000): 18976457,
        ('New York', 2010): 19378102}
pd.Series(data)

### 1.显式地创建多级索引

# 有点zip打包出来的效果
pd.MultiIndex.from_arrays([['a', 'a', 'b', 'b'], [1, 2, 1, 2]])

# 直接把DataFrame的数据拿来当多重索引
pd.MultiIndex.from_frame(df)

# 这个比较直接
pd.MultiIndex.from_tuples([('a', 1), ('a', 2), ('b', 1), ('b', 2)])

# 用笛卡尔积
pd.MultiIndex.from_product([['a', 'b'], [1, 2]])

### 多级索引的等级名称

给多级索引加上一些名称,看起来更加便利

pop.index

pop.index.names = ['stats', 'year']
pop

# 在unstacks的时候还在
pop.unstack()

### 3 多级列索引

既然有多级行索引,那必然存在多级列索引

# 多级的行索引,应该是4行
index = pd.MultiIndex.from_product([[2013, 2014], [1, 2]],
                                   names=['year', 'visit'])
# 多级的列索引,应该有6列
columns = pd.MultiIndex.from_product([['Bob', 'Guido', 'Sue'], ['HR', 'Temp']],
                                     names=['subject', 'type'])
# 创建一个4行5列的数据
data = np.round(np.random.randn(4, 6), 1)
data[:, ::2] *= 10
data += 37

# 创建DataFrame对象
health_data = pd.DataFrame(data=data, columns=columns, index=index)
health_data

从上面可以看出,多行多列的创建是多么的难。上面就创建了4维数据,4个维度分别包括姓名,检查的项目,检查的年份,检查的次数
取数据也很简单,跟着我:

# 简单的检索信息
health_data['Guido']

## 3.6.3 多级索引的取值与切片

对MulitIndex的取值和切片很直观,可以把索引看成额外增加的维度

### 1.Series多级索引

pop

# 取单个元素
pop['California',2000]

# 局部取值(partial indexing) 返回一个新的Series
pop['California']

# 局部切片,但要求MultiIndex是按序列排序的
pop.loc['California':'New York']

# 可以第一层索引空切片
pop[:, 2000]

# 布尔掩码取值
pop[pop>22000000]

# 花哨取值
pop[['California','Texas']]

### 2.DataFrame多级索引

health_data

DataFrame的基本索引是列索引,因此Series中多级索引的用法到了DataFrame就应用在列上了。

health_data['Guido', 'HR']

h = health_data['Guido']
print(type(h))
h

loc与iloc的索引辅助方法也可以使用

# 前两行 两列的数据
health_data.iloc[:2,:2]

# 花哨取值
health_data.loc[:, ['Bob', 'Sue']]

# 特定列取值
health_data.loc[:, ('Bob', 'Temp')]

这种索引元祖的用法不是很方便,如果在元祖中使用切片还会导致语法错误:

health_data.loc[(:,1), (:, 'HR')]

# 通过pd.IndexSlice来对多索引的行与列选定专门的定位
idx = pd.IndexSlice
health_data.loc[idx[:, 1], idx[:, 'HR']]

## 3.6.4多级索引行列转换

尝试多级索引,索引无序的情况下切片会如何

# 创建一个多重索引无序的Series
index = pd.MultiIndex.from_product([['a','c','b'], [1,2]])
data = pd.Series(np.random.rand(6),index=index)
data.index.names = ['char', 'int']
data

try:
    print(data.loc['a':'b'])
except KeyError as e:
    print(type(e))
    print(e)

# 对索引排序,重新返回
data = data.sort_index()
data

data.loc['a':'b']

### 2.索引stack与unstack

stack与unstack可以设置索引的层级

pop

# 设置level参数,我的理解就是联合索引的第几列数据变成DataFrame的colums数据,当然从0开始
pop.unstack()

# 跟默认的输出一样,默认参数为-1
pop.unstack(level=1)

pop.unstack(level=0)

### 3.索引的设置与重置

前面书中使用了方法reindex()可以替换掉原索引,这里将介绍层级转换的另一种方法rest_index
如果在上面的人口数据Series中使用该方法,则会生成一个列便签种包含之前行索引标签stats与year的DataFrame

pop

pop.reset_index()

# 通过name属性为列设置名
pop_flat = pop.reset_index(name='population')
pop_flat

上面这样的DataFrame要是能够直接转换成多重索引的DataFrame也可以直接通过set_inex实现

pop_flat.set_index(['stats', 'year'])

## 3.6.5多级索引的数据累计方法

health_data

# 通过设置level的参数来选定需要参与计算的数据
data_mean = health_data.mean(level='year')
data_mean

# 多从索引可以填写多重索引的名称,也就是为多重索引的索引编号
health_data.mean(level=0)

# 通过设置axis来选择坐标轴,也就是axis=0,折叠水平坐标,纵坐标不变,横坐标压缩,当选择axis为1时,那就是纵坐标折叠,横坐标不变了。
health_data.mean(axis=1, level=1)

health_data.mean(axis=1, level='type')

 03.07合并数据集:Concat与Append操作

# 合并数据集:Concat与Append操作

# 导入Pandas与NumPy
import pandas as pd
import numpy as np

# 定义一个创建DataFrame的函数
def make_df(cols, ind):
    data = {c: [str(c) + str(i) for i in ind]
        for c in cols
    }
    return pd.DataFrame(data, ind)
make_df('ABC', range(3))

## 3.7.1 知识回顾:NumPy数组的合并

合并Series与DateFrame与合并NumPy数组基本相同,后者通过np.concatenate函数完成。

x = [1, 2, 3]
y = [4, 5, 6]
z = [7, 8, 9]
ll = np.concatenate([x, y, z])
ll

concatenate通过axis参数选择合并的方向

x = [[1, 2],
     [3, 4]]
# x=1 在纵坐标连接
np.concatenate([x, x], axis=1)

## 3.7.2通过pd.cancat实现简易合并

Pandas有pd.cancat()函数与np.concatenate语法类似,但参数多很多

```python
# Signature in Pandas v0.18
pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False,
          keys=None, levels=None, names=None, verify_integrity=False,
          copy=True)
```

简单合并一维的Series或DataFrame对象

ser1 = pd.Series(['A','B','C'],index=[1,2,3])
ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
# 合并在一起,默认axis=0
pd.concat([ser1,ser2], axis='rows')

# 合并DataFrame
df1 = make_df('AB', [1, 2])
df2 = make_df('AB', [3, 4])
display(df1, df2, pd.concat([df1, df2]))

# 通过axis的设置合并列,行数据要相等
df3 = make_df('AB', [0, 1])
df4 = make_df('CD', [0, 1])
display(df3, df4, pd.concat([df3, df4], axis='columns'))

# 也可以通过数字设置,效果一样
df3 = make_df('AB', [0, 1])
df4 = make_df('CD', [0, 1])
display(df3, df4, pd.concat([df3, df4], axis=1))

### 1.索引重复

pd.concatenate与pd.cancat最主要的差异之一就是合并会保留索引,即使索引是重复的

x = make_df('AB', [0, 1])
y = make_df('AB', [2, 3])
# y的索引复制x的
y.index = x.index  # make duplicate indices!
display(x, y, pd.concat([x, y]))

### 捕捉索引重复的数据

# 通过设置verify_integrity的值为True,当索引有重复时,上浮错误
try:
    pd.concat([x, y], verify_integrity=True)
except ValueError as e:
    print(f'ValueError {e}')

# 忽略索引,ignore_index参数,有时候索引用处不大的情况下,自动更改索引
print(x), print(y); print(pd.concat((x,y), ignore_index=True))

# 增加多级索引,通过key参数,设置多级索引
print(x);print(y);print(pd.concat((x,y), keys=['x','y']))

### 2.类似join的合并

简单看了书中的介绍,感觉跟数据的内联与外联差不多,默认是外联的

df5 = make_df('ABC', [1, 2])
df6 = make_df('BCD', [3, 4])
# 默认外联,没有数据的地方为NaN
display(df5, df6, pd.concat([df5, df6]))

# 通过设置join参数设置为'inner',数据合并columns为并集,在sql里面为内联
print(df5);print(df6);print(pd.concat((df5,df6),join='inner'))

# 可以指定需要显式的columns的DataFrame源,也就像sql里面的左联,右联
# 我使用版本1.0.5已经没有这个join_axes参数了
print(pd.__version__)
print(df5);print(df6);print(pd.concat((df5,df6),join_axes=[df5.columns]))

### 3.append()方法

Series与Pandas对象带有append方法,也可以实现合并的效果。df1.append(df2)与pd.concat([df1, df2])效果一样

print(df1);print(df2);print(df1.append(df2))

Python列表中append()和extend()方法不同,Pandas的append()不直接更新原有对象的值,而是为合并后的数据创建一个新对象。因此,它不能称之为一个非常高效的解决方案,因此每次合并都需要重新创建索引和数据缓存。总之,如果你需要进行多个append操作,还是建议显创建一个DataFrame列表,然后用concat()函数一次性解决所有合并任务
原文地址:https://www.cnblogs.com/sidianok/p/13827555.html