Pandas笔记--《Python数据科学手册》

代码可直接执行

 

import numpy as np 
import pandas as pd 
import os,time


print("Pandas的Series对象:")
#Pandas 的Series 对象是一个带索引数据构成的一维数,用一个数组创建Series 对象.
data = pd.Series([0.25, 0.5, 0.75, 1.0])
print(data)#out类似于enumerate函数的索引+数据
#Series对象将一组数据和一组索引绑定在一起,我们可以通过values 属性和index 属性获取数据
print("values:\n",data.values)
print("index:\n",data.index)#Out[4]: RangeIndex(start=0, stop=4, step=1)
data[1]#Out[5]: 0.5
print(data[1:3])#切片时会同时显示索引
"""
NumPy 数组通过隐式定义的整数索引获取数值,而Pandas 的Series 对象用一种显式定义的索引与数值关联。
显式索引的定义让Series 对象拥有了更强的能力。索引可以是任意想要的类型,例如字符串定义索引:
"""
data = pd.Series([0.25, 0.5, 0.75, 1.0],
            index=['a', 'b', 'c', 'd'])
print("\n字符串索引:\n",data)

#可以把series看出一种特殊的字典,Series对象其实是一种将类型键映射到一组类型值的数据结构。
population_dict = {'California': 38332521,
                    'Texas': 26448193,
                    'New York': 19651127,
                    'Florida': 19552860,
                    'Illinois': 12882135}
population = pd.Series(population_dict)#用Python 的字典创建一个Series对象(最后都会返回类型信息)
print(population)#Pandas Series 的类型信息使得它在某些操作上比Python 的字典更高效
population['California']
print(population['Texas':'Florida'])#除了支持正常字典获取,还支持数组形式的操作

print("\n创建Series对象")
"""常规创建pd.Series(data, index=index)index 是一个可选参数,data 参数支持多种数据类型,
pd.Series({2:'a', 1:'b', 3:'c'})data可以是字典,index默认是排序字典的键"""

a=pd.Series(5, index=[100, 200, 300])#data也可以是标量创建Series时会重复填充到每个索引上
print(a)
#每一种形式都可以通过显式指定索引筛选需要的结果:
a=pd.Series({2:'a', 1:'b', 3:'c'}, index=[3, 2])#注意这里Series对象只会保留显式定义的键值对。
print(a)


print("\nPandas的DataFrame对象")
"""
你可以把二维数组看成是有序排列的一维数组一样,你也可以把DataFrame 
看成是有序排列的若干Series 对象。这里的“排列”指的是它们拥有共同的索引。
"""
area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297,
'Florida': 170312, 'Illinois': 149995}
area = pd.Series(area_dict)#美国五个州面积的数据创建一个新的Series
#下面再结合之前创建的population 的Series 对象,用一个字典创建一个包含这些信息的二维:
states = pd.DataFrame({'population': population,
                        'area': area})
print(states)
print("获取行索引:\n",states.index)#返回行索引
print("获取列索引:\n",states.columns)#DataFrame 还有一个columns 属性,是存放列标签的Index 对象
#DataFrame 可以看作一种通用的NumPy 二维数组,它的行与列都可以通过索引获(数字索引或数字切片获取)
#可以将Series看成特殊的字典一个键映射一个值,而DataFrame 是一列映射一个Series 的数据

print("\narea:\n",states['area'])#返回area一列的数据以及行索引(列索引为KEY,对应列数组为value)
#DataFrame与np数组不同的是,np的data[0]返回的是行,而dataframe的data["col0"]返回的是列
print("\n获取行:\n",states[0:2])#  or print(states["California":"New York"])
#print(states[0])#注意这样会错误,因为没有0这个key,可以写成[0:1]这样来获取行信息!!!!!!!

print("\n创建DataFrame对象")

print("(1) 通过单个Series 对象创建:")
a=pd.DataFrame(population, columns=['population'])#population是上面创建的series对象
print(a)

print("\n(2) 通过字典列表创建:")
#任何元素是字典的列表都可以变成DataFrame(说的是列表内嵌套的是字典元素)
data = [{'a': i, 'b': 2 * i} for i in range(3)]
print(data)
a=pd.DataFrame(data)
print(a)
#即使字典中有些键不存在,Pandas 也会用缺失值NaN(不是数字,not a number)来表示:
a=pd.DataFrame([{'a': 1, 'b': 2}, {'b': 3, 'c': 4}])#!!!!!!!!!
print(a)

#(3) 通过Series 对象字典创建:(上面第一个例子)

print("\n(4) 通过NumPy 二维数组创建:")
a=pd.DataFrame(np.random.rand(3, 2), columns=['foo', 'bar'], index=['a', 'b', 'c'])
print(a)

print("\n(5) 通过NumPy 结构化数组创建:")
A = np.zeros(3, dtype=[('A', 'i8'), ('B', 'f8')])
print(A)
B=pd.DataFrame(A)
print(B)
#感觉通常情况下用4 5方法创建比较多。

print("\nPandas的Index对象")
"""Pandas的Index 对象可以将它看作是一个不可变数组或有序集合
(实际上是一个多集,因为Index 对象可能会包含重复值)"""
ind = pd.Index([2, 3, 5, 7, 11])
print(ind)
print(ind[1])
print(ind[::2])
#Index对象与NumPy数组之间的不同在于,Index对象的索引是不可变的,不能通过通常的方式进行调整:
#ind[1] = 0会报错,Index 对象的不可变特征使得多个DataFrame 和数组之间进行索引共享时更加安全
#Index 对象遵循Python 标准库的集合(set)数据结构的许多习惯用法,包括并集、交集、差集等:
indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])
print(indA & indB)
print(indA | indB)
print(indA ^ indB)
#这些操作还可以通过调用对象方法来实现,例如indA.intersection(indB)。

print("\n数据取值与选择")
#Series数据选择方法:,Series 对象与一维NumPy 数组和标准Python 字典在许多方面都一样。
print("将Series看作字典:")
data = pd.Series([0.25, 0.5, 0.75, 1.0], index=['a', 'b', 'c', 'd'])
print('a' in data)
print(data.keys())
print(list(data.items()))
data['e'] = 1.25#Series对象还可以用字典语法调整数据(修改或新增)

print("\n将Series看作一维数组")
#具备和NumPy 数组一样的数组数据选择功能,包括索引、掩码、花哨的索引等操作
print(data['a':'c'])#相当于np数组的[1:2]只是index变成了可变的多类型和显性显示
print(data[0:2])#!!!1还可以用隐式整数索引作为切片
#使用显式索引作切片时,结果包含最后一个索引;而当使用隐式索引结果不包含最后一个索引

a=data[(data > 0.3) & (data < 0.8)]# 掩码
print("掩码:\n",a)
print("花哨的索引\n",data[['a', 'e']])# 花哨的索引

print("\n索引器:loc、iloc和ix")
"""切片和取值的习惯用法经常会造成混乱如果你的Series 是显式整数索引,那
么data[1] 这样的取值操作会使用显式索引,而data[1:3] 这样的切片操作却会使用隐式
索引。由于整数索引很容易造成混淆,所以Pandas 提供了一些索引器(indexer)属性来作为取值
的方法"""
data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])#这种类型的索引会造成混乱
print("\nloc 属性:\n",data.loc[1:3])#第一种索引器是loc 属性,表示取值和切片都是显式的

print("\niloc 属性:\n",data.iloc[1:3])#第二种是iloc属性,表示取值和切片都是Python 形式隐式索引

#第三种取值属性是ix,它是前两种索引器的混合形式ix 索引器主要用于DataFrame 对象

print("\nDataFrame数据选择方法")
#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})
data = pd.DataFrame({'area':area, 'pop':pop})
print(data)

print("\n1.将DataFrame看作字典:")
"""把DataFrame 当作一个由若干Series 对象构成的字典两个Series 
分别构成DataFrame 的一列,可以通过对列名进行字典形式的取值获取数据:"""
print(data['area'])
data.area#纯字符串列名的数据可以用属性形式
print(data.area is data['area'])#用==会进行每项对比,得出布尔值
#属性形式的数据选择不是通用的,如果列名不是纯字符串,或者列名与DF的方法同名,那么就不能用属性索引
#例如data.pop is data['pop'] out:false ,DataFrame 有一个pop()名方法
#另外,应该避免对用属性形式选择的列直接赋值

data['density'] = data['pop'] / data['area']#用字典形式修改添加字典内容。
print(data)

print("\n将DataFrame看作二维数组")
#可以把DataFrame 看成是一个增强版的二维数组,用values 属性按行查看数组数据:
print(data.values)
print("行列转置:\n",data.T)
#通过字典形式对列进行取值显然会限制我们把DataFrame 作为NumPy 数组可以获得的能力:
#print(data.values[0])获取行 ;#print(data['area'])#获取列

#因此,在进行数组形式的取值时,我们就需要用Pandas 索引器loc、iloc 和ix 了
#通过iloc 索引器,我们就可以像对待NumPy 数组一样索引Pandas的底层数组
print("iloc:\n",data.iloc[:3, :2])#获取隐形索引的第前3行和前2列
print("loc:\n",data.loc[:'Florida', :'pop'])#使用显型索引
#使用ix 索引器可以实现一种混合效果
print("ix:\n",data.ix[:3, :'pop'])#ix 索引器在整数索引的处理容易让人混淆
#loc 索引器中结合使用掩码与花哨的索引方法:
a=data.loc[data.density > 100, ['pop', 'density']] #`!!!!!!!!!!
print(a)

data.iloc[0, 2] = 90#任何一种取值方法都可以用于调整数据和NumPy 的常用方法是相同的
print(data)

print("\n其他取值方法")
#如果对单个标签取值就选择列,而对多个标签用切片就选择行:
print(data['Florida':'Illinois'])#对多个标签用切片就选择行
#切片也可以不用索引值,而直接用行数来实现:
print(data[1:3])
#掩码操作也可以直接对每一行进行过滤,而不需要使用loc 索引器:
print(data[data.density > 100])
# pd对象[]内直接切片或数字index的都是取行;单个标签取列或[["a","b"]]两个括号内的内容取列

print("\nPandas数值运算方法")
"""对于一元运算(像函数与三角函数),这些通用函数将在输出结果中保留索
引和列标签;而对于二元运算(如加法和乘法),Pandas 在传递
通用函数时会自动对齐索引进行计算。这就意味着,保存数据内容与组合不同来源的数
据——两处在NumPy 数组中都容易出错的地方——变成了Pandas 的杀手锏"""

#所谓一元和二元运算,说的是参与运算的对象或者变量的数量是一个还是两个
print("通用函数:保留索引(一元运算)")
rng = np.random.RandomState(42)
ser = pd.Series(rng.randint(0, 10, 4))#创建4个0-10的随机数组成数组,然后将数组转化为Series对象
print(ser)

df = pd.DataFrame(rng.randint(0, 10, (3, 4)), columns=['A', 'B', 'C', 'D'])
print(df)

#下面两个对象使用np通用函数生成结果是另一个保留索引的pd对象。
print(np.exp(ser))#e**x次方
print(np.sin(df * np.pi / 4))#!!!!注意这俩个都是用np.xx的np通用函数
#numpy 通用函数都可以按照类似的方式使用。!!!!

print("\n通用函数:索引对齐(二元运算)")

print("Series索引对齐")
#整合两个数据源的数据,一个是美国面积最大的三个州的面积数据,一个是美国人口最多的三个州的人口数据
area = pd.Series({'Alaska': 1723337, 'Texas': 695662,
                  'California': 423967}, name='area')
population = pd.Series({'California': 38332521, 'Texas': 26448193,
                        'New York': 19651127}, name='population')

print(population / area)#用人口除以面积会得到这样的结果(即使顺序不同,也会根据行索引名来除)
#结果数组的索引是两个输入数组索引的并集,对于缺失位置的数据,Pd会用NaN填充,
A = pd.Series([2, 4, 6], index=[0, 1, 2])
B = pd.Series([1, 3, 5], index=[1, 2, 3])
print(A+B)
#如果不想用NAN的结果,可以用适当的对象方法代替运算符:
print(A.add(B, fill_value=0))#A.add(B)等价于A + B,fill_value为自定义A或B缺失数据的填充

print("DataFrame索引对齐")
#在计算两个DataFrame 时,索引对齐规则也同样会出现在共同(并集)列中
A = pd.DataFrame(rng.randint(0, 20, (2, 2)), columns=list('AB'))
B = pd.DataFrame(rng.randint(0, 10, (3, 3)), columns=list('BAC'))
print(A+B)#结果的索引会自动按顺序排列

#用A中所有值的均值来填充缺失值
fill = A.stack().mean()#计算A 的均值需要用stack 将二维数组压缩成一维数组
print(A.add(B, fill_value=fill))
"""
需要插入参数的时候可以用oandas方法替代运算符:
表3-1:Python运算符与Pandas方法的映射关系
  Python运算符         Pandas方法
  +                   add()
  -                   sub()、subtract()
  *                   mul()、multiply()
  /                   truediv()、div()、divide()
  //                  floordiv()
  %                   mod()
  **                  pow()
"""

print("\n通用函数:DataFrame与Series的运算")

A = rng.randint(10, size=(3, 4))
print(A)
print(A - A[0])#根据np数组的广播规则进行运算,会按行计算

df = pd.DataFrame(A, columns=list('QRST'))
print("\nPandas运算:\n",df - df.iloc[0])#在Pandas 里默认也是按行运算的
#如果你想按列计算,那么就需要利用前面介绍过的运算符方法,通过axis 参数设置:
a=df.subtract(df['R'], axis=0)
print("\n按列计算:\n",a)

print(df.iloc[0, ::2])#series对象
print(df - df.iloc[0, ::2])
#行列保留和对齐是pandas的优势

print("\n处理缺失值")
"""大多数语言处理缺失数据是有两种方法:一种方法是通过一个覆盖全局的掩码表示缺失值,
另一种方法是用一个标签值(sentinel value)表示缺失值;
Pandas 最终选择用标签方法表示缺失值,包括两种Python 原有的缺失值:浮点数据类型的NaN 值,
以及Python 的None 对象。------关于缺失值详解参考P106"""

print("None:Python对象类型的缺失值")
#none是一个Py单体对象,经常在代码中表示缺失值。它是一个py的对象所以不能作为任何np\pd数组的缺失值
vals1 = np.array([1, None, 3, 4])
print(vals1)#ipython out:array([1, None, 3, 4], dtype=object),
"""这里dtype=object 表示NumPy 认为由于这个数组是Python 对象构成的,对数据的任何操作
最终都会在Python 层面完成,这种类型比其他原生类型数组要消耗更多的资源(时间)。"""

# vals1.sum()使用Python对象构成的数组进行累计操作时sum() 或者min(),会出现类型错误,
# Python 中没有定义整数与None 之间的加法运算。

print("\nNaN:数值类型的缺失值")

vals2 = np.array([1, np.nan, 3, 4])
print(vals2.dtype)#np为其选择数据类型为float64,这个数组会被编译成C代码从而实现快速操作
#可以把NaN看作是一个数据类病毒——它会将与它接触过的数据同化。无论和NaN进行何种操作,最终结果都是NaN
#很多情况下需要与nan运算时,结果是nan是不合理的  例如sum()运算时。
print(vals2.sum(), vals2.min(), vals2.max())

#NumPy提供了一些特殊的累计函数,它们可以忽略缺失值的影响:
print(np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2))#np笔记P270
#!!!NaN 是一种特殊的浮点数,不是整数、字符串以及其他数据类型。

print("\nPandas中NaN与None的差异")
#pandas把NaN和none看成可以等价交换的,适当时候会将两者进行替换:
a=pd.Series([1, np.nan, 2, None])
print(a)#none被替换为Nan

#Pandas 会将没有标签值的数据类型自动转换为NA(整形缺失值)。
x = pd.Series(range(2), dtype=int)
print(x)

x[0] = None
print(x)#,除了将整型数组的缺失值强制转换为浮点数,Pandas 还会自动将None 转换为NaN。

"""
Pandas 对NA 缺失值进行强制转换的规则如表3-2 所示。
表3-2:Pandas对不同类型缺失值的转换规则

  类型          缺失值转换规则           NA标签值
  floating      浮点型无变化               np.nan
  object        对象类型无变化             None 或np.nan
  integer       整数类型强制转换为         float64 np.nan
  boolean       布尔类型强制转换为         object None 或np.nan
  
需要注意的是,Pandas 中字符串类型的数据通常是用object 类型存储的。
"""

print("\n处理缺失值")
"""Pandas 提供了一些方法来发现、剔除、替换数据结构中的缺失值,
  isnull()  创建一个布尔类型的掩码标签缺失值。
  notnull() 与isnull() 操作相反。
  dropna()返回一个剔除缺失值的数据。
  fillna()返回一个填充了缺失值的数据副本。
"""
print("1. 发现缺失值")
data = pd.Series([1, np.nan, 'hello', None])
print(data.isnull())#返回布尔类型掩码数据,data.notnull()返回相反的结果
print(data[data.notnull()])#前面内容学习过,布尔类型掩码数组可以直接作为Series或DF的索引使用

print("\n2. 剔除缺失值")
print(data.dropna())#在series上使用方法

df = pd.DataFrame([[1, np.nan, 2],
                   [2, 3, 5],
                   [np.nan, 4, 6]])
#不能从DF中单独剔除一个值,要么是剔除缺失值所在的整行,要么是整列。
print(df.dropna())#默认情况下dropna会剔除虽有包含缺数的整行数据
#设置按不同的坐标轴剔除缺失值,比如axis=1(或axis='columns')会剔除任何包含缺失值的整列数据
print(df.dropna(axis='columns'))

print("\n用how和thresh参数剔除")
#可以通过设置how 或thresh 参数来设置剔除行或列缺失值的数量阈值,默认设置是how='any'
df[3] = np.nan
print(df)
#通过axis 设置坐标轴)。你还可以设置how='all',这样就只会剔除全部是缺失值的行或列
a=df.dropna(axis='columns', how='all')
print(a)
#通过thresh 参数设置行或列中非缺失值的最小数量:
b=df.dropna(axis='rows', thresh=3)#这里axis是行,就是根据行的非NAN数值为3以下可以剔除相应行
print(b)

print("\n3. 填充缺失值")
#series的数据填充:
data = pd.Series([1, np.nan, 1, None, 3], index=list('abcde'))
print(data.fillna(0))#用0来填充缺失值
print("往前填充\n",data.fillna(method='ffill'))#用缺失值前面的有效值来从前往后填充(forward-fill)
print("往前填充\n",data.fillna(method='bfill'))#用缺失值后面的有效值来从后往前填充(back-fill)
#如果前面也是nan则再往前

#dataframe的操作方法与Series 类似,只是在填充时需要设置坐标轴参数axis:
print("\ndataframe填充方法\n",df)
print(df.fillna(method='ffill', axis=1))
#假如在从前往后填充时,需要填充的缺失值前面没有值,那么它就仍然是缺失值。
print(df.fillna(method='bfill', axis=0))


print("\n3.6 层级索引")

print("多级索引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)
print(pop)
#通过元组构成的多级索引,你可以直接在Series 上取值或用切片查询数据
print(pop[('California', 2010):('Texas', 2000)])
#假如需要选择所有2000 年的数据需要比较麻烦的方法:
print("\n",pop[[i for i in pop.index if i[1] == 2010]])

print("\nPandas多级索引")
#用元组表示索引其实是多级索引的基础,Pandas的MultiIndex 类型提
index = pd.MultiIndex.from_tuples(index)#用元组创建一个多级索引
print(index)
pop = pop.reindex(index)#索引重置(reindex)为MultiIndex,(reindex()函数可以重置数组索引)
print(pop)

print(pop[:,2000])#在可以直接用第二个索引获取2010 年的全部数据
print(pop["California",2000])#得出结果的第一列每个空格与上面索引相同

print("\n3. 高维数据的多级索引")
# unstack() 方法可以快速将一个多级索引的Series 转化为普通索引的DataFrame:
pop_df = pop.unstack()
print(pop_df)

print("\nstack():\n",pop_df.stack())#stack() 方法实现相反的效果:
"""
可以用含多级索引的一维Series 数据表示二维数据,那么就可以用Series 或DataFrame 
表示三维甚至更高维度的数据。多级索引每增加一级,就表示数据增加一维,利用这一特点就可以
轻松表示任意维度的数据了
"""
#这种带有MultiIndex 的对象,增加一列就像DataFrame 的操作一样简单:!!!!!!!!!!!!
pop_df = pd.DataFrame({'total': pop,'under18': [9267089, 9284094,
                                                4687374, 4318033,
                                                5906301, 6879014]})
print(pop_df)
#3.4章所有的pd通用函数其他功能也同样适用于层级索引
f_u18 = pop_df['under18'] / pop_df['total']
print(f_u18.unstack())

print("\n多级索引的创建方法")
#为Series 或DataFrame 创建多级索引最直接的办法就是将index参数设置为至少二维的索引数组
df = pd.DataFrame(np.random.rand(4, 2),
                  index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
                  columns=['data1', 'data2'])
print(df)#MultiIndex 的创建工作将会在后台完成。

#将元组作为键的字典传递给Pandas, Pandas 也会默认转换为MultiIndex
data = {('California', 2000): 33871648,
        ('California', 2010): 37253956,
        ('Texas', 2000): 20851820,
        ('Texas', 2010): 25145561,
        ('New York', 2000): 18976457,
        ('New York', 2010): 19378102}
print(pd.Series(data))

print("显式地创建多级索引")
#通过一个有不同等级的若干简单数组组成的列表来构建MultiIndex:
a=pd.MultiIndex.from_arrays([['a', 'a', 'b', 'b'], [1, 2, 1, 2]])
print(a)
#通过包含多个索引值的元组构成的列表创建MultiIndex:
pd.MultiIndex.from_tuples([('a', 1), ('a', 2), ('b', 1), ('b', 2)])
#可以用两个索引的笛卡尔积(Cartesian product)创建MultiIndex:
pd.MultiIndex.from_product([['a', 'b'], [1, 2]])
#可以直接提供levels(包含每个等级的索引值列表的列表)和labels(包含每个索引值标签列表的列表)创建:
pd.MultiIndex(levels=[['a', 'b'], [1, 2]], labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
"""创建Series 或DataFrame 时,可以将这些对象作为index 参数,或者通过.reindex() 方法
更新Series 或DataFrame 的索引。"""

print("\n多级索引的等级名称")
#给MultiIndex 的等级加上名称会为一些操作提供便利;1.在multiindex通过name参数设置,2.下面:
pop.index.names = ['state', 'year']
print(pop)

print("多级列索引")
index = pd.MultiIndex.from_product([[2013, 2014], [1, 2]],
                                    names=['year', 'visit'])
columns = pd.MultiIndex.from_product([['Bob', 'Guido', 'Sue'], ['HR', 'Temp']],
                                      names=['subject', 'type'])
#创建多行列索引方法和上面一样
data = np.round(np.random.randn(4, 6), 1)#生成一个4X6的数组,最后1表示保留浮点数后一位。
data[:, ::2] *= 10
data += 37
#创建dataframe
health_data = pd.DataFrame(data, index=index, columns=columns)
print(health_data)#四维数据
print("1:\n",health_data['Guido'])
print("2:\n",health_data['Guido']["Temp"])#
print(health_data.values)
print(health_data.stack().stack())#用了两个堆叠
print(health_data.unstack())#展开
# 规律都是从内部解开,然后插入内部

print("\n多级索引的取值与切片")
print("\nSeries多级索引\n",pop)
print(pop['California', 2000])#pop['California']、pop[:, 2000],pop.loc['California':'New York']
#通过布尔掩码选择数据:
print("\n布尔取值\n",pop[pop > 22000000])#布尔掩码一般返回ture的对应行号
#用花哨的索引选择数据:pop[['California', 'Texas']]#里面是列表

print("\nDataFrame多级索引:")
print(health_data)#DataFrame 的基本索引是列索引!!!!!!
print(health_data['Guido', 'HR'])

#可以通过使用索引器loc、iloc 和ix:
print("\n索引器\n",health_data.iloc[:2, :2])#使用隐性索引来提取数据,索引器会将多维数据当作二维数据来处理
print(health_data.loc[:, ('Bob', 'HR')])#可以传递多个层级的索引元组
#health_data.loc[(:, 1), (:, 'HR')]这种方法在元组中使用切片会导致语法错误

idx = pd.IndexSlice
a=health_data.loc[idx[:, 1], idx[:, 'HR']]#indexslice对象是pd专门用来解决切片问题
print(a)
# 规律:由外到内切片

print("\n多级索引行列转换")
#1. 有序的索引和无序的索引;如果创建时MultiIndex时不是有序的索引,那么大多数切片操作都会失败
index = pd.MultiIndex.from_product([['a', 'c', 'b'], [1, 2]])#创建一个不按字典顺序排列的多级索引!!!
data = pd.Series(np.random.rand(6), index=index)
data.index.names = ['char', 'int']
print(data)
#data['a':'c']~~~~会出先keyerror错误,因为上面创建MultiIndex时给出的列表不是字典顺序,a,c,b
#局部切片和许多其他相似的操作都要求MultiIndex 的各级索引是有序的(即按照字典顺序由A 至Z)。

data = data.sort_index()#,Pandas 提供了许多便捷的操作完成排序,如sort_index() 和sortlevel() 方法
print(data['a':'b'])#排序后,局部切片正常

#2. 索引stack与unstack;通过level 参数设置转换的索引层级(0为最外层)
print(pop)
print("\nlevel=0\n",pop.unstack(level=0))
print("\nlevel=1\n",pop.unstack(level=1))

#3. 索引的设置与重置
pop_flat = pop.reset_index(name='population')#通过reset_index 方法行列标签转换!!!!!!!!
print(pop_flat)#生成一个列标签中包含之前行索引标签state 和year 的DataFrame(把标签变成列数组)
print(pop_flat[0:3]["population"])#因为population已经变成列,所以不能用pop_flat[0:3,"population"]
# 用多级索引处理完数据之后再reset_index刷新并自动填充之前的多级索引,成为新的df数据,也可以用来刷新索引

print(pop_flat.set_index(['state', 'year']))#于上面相反!!!可以用来创建多级索引DF。
#将类似这样的原始输入数据的列直接转换成MultiIndex,(类始于excel表上创建DF一样,根据时间等序列为列表签)


print("\n多级索引的数据累计方法\n")

print(health_data)
#置参数level 实现对数据子集的累计操作(year是前面创建MultiIndex时的参数name时赋予的)
data_mean = health_data.mean(level='year')
print(data_mean)

#再设置axis 参数,就可以对列索引进行类似的累计操作
print(data_mean.mean(axis=1, level='type'))


print("\n合并数据集:Concat与Append操作")
#series合并
x = [1, 2, 3]
y = [4, 5, 6]
print(np.concatenate([x, y]))
#daaframe合并
x = [[1, 2],
    [3, 4]]
print(np.concatenate([x, x], axis=1))

print("\npandas通过pd.concat实现简易合并")
def make_df(cols, ind):
  """一个简单的DataFrame"""
  data = {c: [str(c) + str(i) for i in ind] for c in cols}#先外往内!!二维数组的列表生成式。
  print(data)
  return pd.DataFrame(data, ind)
# DataFrame示例
print(make_df('ABC', range(3)))

#pd.concat()可以简单地合并一维的Series 或DataFrame 对象,与np.concatenate() 合并数组一样
ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3])
ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
print("\n合并series\n",pd.concat([ser1, ser2]),"")

"""
concat()所有的参数:
pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False,
keys=None, levels=None, names=None, verify_integrity=False,
copy=True) """

df1 = make_df('AB', [1, 2])
df2 = make_df('AB', [3, 4])
print(df1); print(df2); print(pd.concat([df1, df2],axis=1,ignore_index=True))#单行用;号可以间隔代码
#默认情况下,DataFrame 的合并都是逐行进行的(默认设置是axis=0)
df3 = make_df('CD', [1, 2])
print("\naxis=1\n",pd.concat([df1, df3],axis=1))#书上的"col"可能版本不一样


print("\n索引重复")
#pd.concatenate 与pd.concat 最主要的差异之一就是Pandas(councat)在合并时后者会保留索引,即使索引是重复的!
#pd.concat() 提供了下面的解决这个问题的方法(行索引和列标签重复都适用):

#(1)可以设置verify_integrity 参数为True,合并时若有索引重复就会触发异常
#(2)设置ignore_index 参数为True,合并时将会创建一个新的整数索引。
print(pd.concat([df1, df2],axis=1,ignore_index=True))
#(3)通过keys 参数来增加多级索引:
print(pd.concat([df1, df1],axis=0,keys=["x","y"]))

print("\n类似join的合并")
df5 = make_df('ABC', [1, 2])
df6 = make_df('BCD', [3, 4])
print(df5); print(df6); 
print(pd.concat([df5, df6],sort=True))#传递sort=True会消去警告(不影响)

#默认情况下缺失的数据会用NaN表示,可以用join 和join_axes 参数设置合并方式
print("\n并集合并\n",pd.concat([df5, df6], join='inner'))#用join='inner' 实现对输入列的交集合并


#设置join_axes 参数,里面是索引对象构成的列表(是列表的列表)
print(df5.columns)
print(pd.concat([df5, df6], join_axes=[df5.columns]))#df5的的列

print("\nappend()方法")#用ipython查看pandas各种函数的使用方法
#Series 和DataFrame 对象都支持append 方法df1.append(df2),效果与pd.append([df1, df2]) 一样
"""
与Python 列表中的append() 和extend() 方法不同,Pandas 的append() 不
直接更新原有对象的值,而是为合并后的数据创建一个新对象。因此,它不能被称之为一
个非常高效的解决方案,因为每次合并都需要重新创建索引和数据缓存。总之,如果你需
要进行多个append 操作,还是建议先创建一个DataFrame 列表,然后用concat() 函数一次
性解决所有合并任务。
"""

print("\n3.8 合并数据集:合并与连接")
#Pandas 的基本特性之一就是高性能的内存式数据连接(join)与合并(merge)操作。(类似于数据库)
#关系代数pd.merge()介绍P129

print("数据连接的类型")
#pd.merge() 函数实现了三种数据连接的类型:一对一、多对一和多对多

df1 = pd.DataFrame({'employee': ['Bob', 'Jake', 'Lisa', 'Sue'],
                    'group': ['Accounting', 'Engineering', 'Engineering', 'HR']})
df2 = pd.DataFrame({'employee': ['Lisa', 'Bob', 'Jake', 'Sue'],
                    'hire_date': [2004, 2008, 2012, 2014]})
print("df1\n",df1); print("df2\n",df2)
df3 = pd.merge(df1, df2)#“employee”列的位置是不一样的,但是pd.merge() 函数会正确处理这个问题。
print("\ndf3",df3)#pd.merge() 会默认丢弃原来的行索引,不过也可以自定义

print("\n多对一连接")
#多对一连接是指,在需要连接的两个列中,有一列的值有重复(结果DataFrame 将会保留重复值。)
df4 = pd.DataFrame({'group': ['Accounting', 'Engineering', 'HR'], 
                    'supervisor': ['Carly', 'Guido', 'Steve']})
print(df4); print(pd.merge(df3, df4))#一对多指这里的多出来的supervisor。

print("\n多对多连接")
#左右两个输入的共同列都包含重复值,那么合并的结果就是一种多对多连接。
df5 = pd.DataFrame({'group': ['Accounting', 'Accounting',
                    'Engineering', 'Engineering', 'HR', 'HR'],
                    'skills': ['math', 'spreadsheets', 'coding', 'linux',
                    'spreadsheets', 'organization']})

print("df5\n",df1); print("df5\n",df5); print("df1合并df5\n",pd.merge(df1, df5))
#留意上面多出来的组合(最大化的组合)

#很多时候需要合并的另个列表标签名不一致,需要设置参数来对应合并的键:
print("\n3.8.3设置数据合并的键")
pd.merge(df1, df2, on='employee')#参数on 设置为一个列名字符串或者一个包含多列名称的列表
#这个参数在两个DF有共同列时使用

#要合并两个列名不同的数据集可以用left_on 和right_on 参数来指定列名:
df3 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],
                    'salary': [70000, 80000, 120000, 90000]})
print(df1); print(df3);
print(pd.merge(df1, df3, left_on="employee", right_on="name"))
#获取的结果中会有一个多余的列,可以通过DataFrame 的drop() 方法将这列去掉
pd.merge(df1, df3, left_on="employee", right_on="name").drop('name', axis=1)

print("\n合并索引:left_index与right_index参数")

df1a = df1.set_index('employee')#将列表索引设置为employee列!!!!
df2a = df2.set_index('employee')
print(df1a); print(df2a)
#可以通过设置pd.merge()中的left_index 和/ 或right_index参数将索引设置为键来实现合并:
print(pd.merge(df1a, df2a, left_index=True, right_index=True))

#DataFrame 实现了join() 方法,它可以按照索引进行数据合并:
print("\njoin()",df1a.join(df2a))

#想将索引与列混合使用,可以通过结合left_index与right_on,或者结合left_on与right_index来实现
print(df1a); print(df3);
print("\n索引与列混合使用\n",pd.merge(df1a, df3, left_index=True, right_on='name'))
"""
关于concat、join、merge的区别:concat可以设置合并的轴,支持数据合并和连接,一般用索引作为
合并依据;join适用于数据连接,一般用索引作为合并依据;merge用于数据连接,可以同时用列表标签
名和索引进行数据连接(通过left_index、right_on等实现;可以用set_index转换列标签为索引)
"""
print("\n设置数据连接的集合操作规则")
#集合操作规则。当一个值出现在一列,却没有出现在另一列时,就需要考虑集合操作规则
df6 = pd.DataFrame({'name': ['Peter', 'Paul', 'Mary'],
                    'food': ['fish', 'beans', 'bread']},
                    columns=['name', 'food'])
df7 = pd.DataFrame({'name': ['Mary', 'Joseph'],
                    'drink': ['wine', 'beer']},
                    columns=['name', 'drink'])
print("df6\n",df6); print("df7\n",df7); 
print("\n内连接:\n",pd.merge(df6, df7, how='inner'))#how参数的交集连接inter是内连接
print("\n外连接:\n",pd.merge(df6, df7, how='outer'))#how参数的并集连接outer是外连接
#左连接(left join)和右连接(right join)返回的结果分别只包含左列和右列(缺失由NAN填充)
print("\n左连接\n",pd.merge(df6, df7, how='left'))


print("\n重复列名:suffixes参数")
#遇到两个输入DataFrame 有重名列的情况:
df8 = pd.DataFrame({'name': ['Jake', 'Bob', 'Lisa', 'Sue'],
                    'rank': [1, 2, 3, 4]})
df9 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],
                    'rank': [3, 1, 4, 2]})
print("df8\n",df8); print("df9\n",df9); 
print(pd.merge(df8, df9, on="name"))#pd.merge() 函数会自动为它们增加后缀_x 或_y

print("\nsuffixes 参数\n",pd.merge(df8, df9, on="name", suffixes=["_L", "_R"]))
#通过suffixes 参数自定义后缀名,suffixes 参数同样适用于任何连接方式三个及三个以上的重复列


print("\n案例:美国各州的统计数据")#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
pop = pd.read_csv('state-population.csv')
areas = pd.read_csv('state-areas.csv')
abbrevs = pd.read_csv('state-abbrevs.csv')

print(pop.head()); print(areas.head()); print(abbrevs.head())#
#df.head() 读取头几条数据,括号内可以具体行数,pandas内函数
merged = pd.merge(pop, abbrevs, how='outer',left_on='stateregion',
                   right_on='abbreviation').drop('abbreviation', 1)#axis=1
#合并pop和abbrevs,得出人口对应州全称,并删除多出的一列
print("\n合并pop和abbrevs\n",merged.head())

print("检查缺失项:\n",merged.isnull().any())#324返回每列缺失情况布尔掩码(人口和州存在缺数)
#.any()这里作用是按照列的总的情况来判断是否缺失(判断列是否存在缺数),没有any会逐行打印所有缺失项

print("\n人口缺失数据:\n",merged[merged['population'].isnull()])#打印出缺人口数据的行
#结果显示2000年前PR(波多黎各)的人口数据缺失

print("\n州全名缺失:\n",merged.loc[merged['state'].isnull(), 'stateregion'].unique())
#unique():返回参数数组中所有不同的值,并按照从小到大排序
#用loc显性索引返回state为nan的行然后匹配显示列索引"stateregion",再去除重复项排序显示

#人口数据中缺数包含波多黎各(PR)和全国总数(USA)快速填充对应的全称:
merged.loc[merged['stateregion'] == 'PR', 'state'] = 'Puerto Rico'
merged.loc[merged['stateregion'] == 'USA', 'state'] = 'United States'
#利用布尔掩码提取'stateregion'等于PR&USA的数据然后用显示索引修改相关项,以填充缺失部分
print("\n填充缺数州名后:\n",merged.isnull().any())

#用相同的规则去合并面积数据,根据数据集共同的state列来合并
final = pd.merge(merged, areas, on='state', how='left')
print(final.head())
#根据merged的state为连接(左连接)合并areas

print("\n检查缺失:\n",final.isnull().any())#area (sq. mi)存在缺失数据
print("\n提取缺失面积:\n",final.loc[final["area (sq. mi)"].isnull(),"state"].unique())
#final['state'][final['area (sq. mi)'].isnull()].unique()#书上写法

final.dropna(inplace=True)#清洗缺失数据
#dropna()返回一个剔除缺失值的数据。缺失为全国面积,删除相缺失项
#参数:inplace=True:不创建新的对象,直接对原始对象进行修改;
#参数:inplace=False:对数据进行修改,创建并返回新的对象承载其修改结果。


print("\n计算人口密度")
data2010 = final.query("year == 2010 & ages == 'total'")#query()函数后面章节有介绍!!!!!
print(data2010.head())# 上面不需要什么掩码筛选之类的,比较方便!!!!

data2010.set_index('state', inplace=True)#对索引进行重置,设置state列为索引
density = data2010['population'] / data2010['area (sq. mi)']#人口密度计算


density.sort_values(ascending=False, inplace=True)#ascending=False降序,直接修改原值
print(density.head())
"""
按索引排序
sort_index(axis=0, level=None, ascending=True, inplace=False, 
    kind='quicksort', na_position='last', sort_remaining=True, by=None)

按数值排序
    DataFrame.sort_values(by=[作为排序依据的列名,可多个], axis=0, ascending=True, 
    inplace=False, kind='quicksort', na_position='last') 
"""
print("\n降序:",density.tail())#与head()相反
#df.head() #头五行    df.tail() #尾五行     df.T #转置


print("\n累计与分组:")

print("\nPandas的简单累计功能")
rng = np.random.RandomState(42)
ser = pd.Series(rng.rand(5))
print("series:\n",ser); print(ser.sum()); print(ser.mean())

#DataFrame 的累计函数默认对每列进行统计:
df = pd.DataFrame({'A': rng.rand(5), 'B': rng.rand(5)})
print("\ndataframe:\n",df); print(df.mean()); 
print(df.mean(axis='columns'))#设置axis 参数,可以对每一行进行统计了
#Pandas 的Series 和DataFrame 支持所有(np270)行中介绍的常用累计函数。

print("\n行星数据:")
import seaborn as sns #Seaborn 程序库,建立在mlp的基础上的2D数据可视化库
planets = sns.load_dataset('planets')#库自带的数据'planets'行星数据
print(planets.shape)#维度结构
print(planets.head())

#describe() 方法可以计算每一列的若干常用统计值
print("\ndescribe() 方法\n",planets.dropna().describe())#先删除缺失项
"""
Pandas 内置的一些累计方法如表3-3 所示。
表3-3:Pandas的累计方法
指标                      描述
count()                   计数项
first()、last()            第一项与最后一项
mean()、median()           均值与中位数
min()、max()               最小值与最大值
std()、var()               标准差与方差
mad()                     均值绝对偏差(mean absolute deviation)
prod()                    所有项乘积
sum()                     所有项求和
DataFrame 和Series        对象支持以上所有方法。
"""

print("\nGroupBy:分割、应用和组合")#P143,类始于透视表
#GroupBy将中间的分割应用和组合过程不需要显式地暴露出来!,只要把操作看成一个整体
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
                  'data': range(6)}, columns=['key', 'data'])
print(df)
print("\n",df.groupby('key'))#根据key来分隔!!!!或者说分组
"""这里返回的不是一个dataframe对象,而是一个DataFrameGroupBy 对象
可以看出一种特殊的dataframe,这种“延迟计算”在没有应用累计函数之前不会计算。"""

print(df.groupby('key').sum())#可以使用Pandas 或NumPy 的任意一种累计函数(例如上面表格)
#对DataFrameGroupBy 对象应用累计函数,它会完成相应的应用/组合步骤并生成结果:

print("\nGroupBy对象")#可以看成是dataframe的集合
print(planets.groupby('method'))#这里是DataFrameGroupBy
print(planets.groupby('method')['orbital_period'])#留意这里返回的是SeriesGroupBy 

#从原来的DF中取某个列名作为一个Series组。与GroupBy 对象一样,直到运行累计函数,才会开始计算:
print("\n中位值:")
print(planets.groupby('method')['orbital_period'].median())
#这里根据method作为分组,然后在各组提取orbital_period列,然后算各组中位数,再组合起来

print("\n按组迭代")
#GroupBy 对象支持直接按组进行迭代,返回的每一组都是Series 或DataFrame。(下面只打印每组结构)
for (method, group) in planets.groupby('method'):#这里的group应该指的是每个分组的本身
  print("{0:30s} shape={1}".format(method, group.shape))#0:30是指定0的位置然后加上分隔30
#shape={1}就是format(method, group.shape)1的位置,显示当前组的维度结构。

print("\n按调用方法")
"""
任何不由GroupBy 对象直接实现的方法直接应用到每一组,无论是DataFrame 还是Series 
对象都同样适用例如,可以用DataFrame 的describe() 方法进行累计,对每一组数据进行描述性统计:
"""
print(planets.groupby('method')['year'].describe())#对每个分组的year进行常规的统计值
"""方法首先会应用到每组数据上,然后结果由GroupBy 组合后返回。另外,
任意DataFrame / Series 的方法都可以由GroupBy 方法调用,从而实现非常灵活强大的操作。"""


print("\n累计、过滤、转换和应用")
#aggregate()、filter()、transform() 和apply() 方法,在数据组合之前实现了大量高效的操作。

rng = np.random.RandomState(0)
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
                    'data1': range(6),
                    'data2': rng.randint(0, 10, 6)},
                    columns = ['key', 'data1', 'data2'])

print("\n基准df:\n",df)

print("1.累计")
#aggregate() 可以支持更复杂的操作,比如字符串、函数或者函数列表,并且能一次性计算所有累计值
print(df.groupby('key').aggregate(['min', np.median, max]))#同时计算各列相关累计统计
#另一种用法就是通过Python 字典指定不同列需要累计的函数
print(df.groupby('key').aggregate({'data1': 'min','data2': 'max'}))

print("\n2.过滤")
#过滤操作可以让你按照分组的属性丢弃若干数据。例如只需要保留标准差超过某个阈值的组
def filter_func(x):
  return x['data2'].std() > 4#std标准差
print(df.groupby('key').std());
print(df.groupby('key').filter(filter_func))
#filter()函数会返回一个布尔值,表示每个组是否通过过滤。A组data2列的标准差不大于4,所以被丢弃了
#filter()也接收一个函数和一个序列;关于filter()函数详细查看廖雪峰笔记287行;】
#filter()函数是过滤每一组的数据,而不是过滤组内的数据!以组为单位进行布尔判断false则抛弃该组

print("\n3.转换")
"""
累计操作返回的是对组内全量数据缩减过的结果,而转换操作会返回一个新的全
量数据。数据经过转换之后,其形状与原来的输入数据是一样的。常见的例子就是将每
一组的样本数据减去各组的均值,实现数据标准化:
"""
# 对组内每个数据进行运算,运算对象可以是每组每列列的运算结果与对应列的每一个数据进行运算:
print(df.groupby('key').transform(lambda x: x - x.mean()))#经过转换后形状与原来输入一样


print("\n4.apply() 方法")
"""apply() 方法让你可以在每个组上应用任意方法。这个函数输入一个
DataFrame,返回一个Pandas 对象(DataFrame 或Series)或一个标量(scalar,单个数
值)。组合操作会适应返回结果类型。
"""
def norm_by_data2(x):
  # x是一个分组数据的DataFrame
  x['data1'] /= x['data2'].sum()#将第一列数据以第二列的和为基数进行标准化:
  return x
print(df.groupby('key').apply(norm_by_data2))#对每个组 进行函数norm_by_data2运算,再返回
#要注意的地方是它总是输入分组数据的DataFrame,返回Pandas 对象或标量。具体如何选择需要视情况而定。


print("\n设置分割的键")
#上面都是根据列名作为分割,现在将列表、数组、Series 或索引作为分组键
L = [0, 1, 0, 1, 2, 0]
print("(1)\n",df.groupby(L).sum())#分组键可以是长度与DataFrame 匹配的任意Series 或列表
print(df.groupby(df['key']).sum())#原理和上面一样,只是提取了key列列表为分组依据

#用字典或Series 将索引映射到分组名称
df2 = df.set_index('key')
mapping = {'A': 'vowel', 'B': 'consonant', 'C': 'consonant'}
print("\n(2)\n",df2); print(df2.groupby(mapping).sum())

#与前面的字典映射类似,将任意Python函数传入groupby,函数映射到索引,然后新的分组输出:
print("\n(3)\n",df2.groupby(str.lower).mean())

#多个有效键构成的列表,任意之前有效的键都可以组合起来进行分组,从而返回一个多级索引的分组结果:
print("\n(4)\n",df2.groupby([str.lower, mapping]).mean())#两个组合起来分组形成多级索引

print("\n分组案例")
print(planets)
decade = 10 * (planets['year'] // 10)#引用的是行星数据,提取年列然后整除10在乘以10,将个位变0
decade = decade.astype(str) + 's' #astype(str)修改数组类型为str
decade.name = 'decade' #修改列名称
print(planets.groupby(['method', decade])['number'].sum().unstack().fillna(0))
"""将卫星数据中的'method'所谓分组,decade为第二分组->提取number列->应用求和-组合输出后
用unstack将多层索引数据转化为dataframe形式(行索引转列索引)->用fillna将DF内的nan值替换为0."""


print("\n3.10 数据透视表")#逻辑和视图方面和excel的数据透视表相似
#可以看作一种多维度的groupby,是分割与组合不是发生在一维索引上,而是在二维网格上(行列同时分组)
titanic = sns.load_dataset('titanic')#Seaborn 程序库,上面行星案例已import
print(titanic.head())#泰坦尼克号乘客信息数据

#统计不同性别乘客的生还率:
print(titanic.groupby('sex')[['survived']].mean())#这里加两个中括号是为了不显示数据类型信息

#统计不同性别与船舱等级的生还情况
print(titanic.groupby(['sex', 'class'])['survived'].aggregate('mean').unstack())
#将可以尝试.aggregate('mean')增加多几个统计项
#unstack()在这里相当于 excel将class从透视表的航标签挪到列标签中。


print("\n数据透视表语法")#实现数据透视表的另一种语法
#Pandas 提供了一个快捷方式pivot_table 来快速解决多维的累计分析任务
#用DataFrame 的pivot_table 实现的效果等同于上一节的管道命令的代码
print(titanic.pivot_table('survived', index='sex', columns='class'))
"""
pivot_table函数具体参数:
pd.pivot_table(data,values=None,index=None,columns=None,aggfunc='mean',
              fill_value=None,margins=False,dropna=True,margins_name='All')

data: A DataFrame object
values: a column or a list of columns to aggregate
index: a column, Grouper, array which has the same length as data, or list of them. Keys to group by on the pivot table index. If an array is passed, it is being used as the same manner as column values.
columns: a column, Grouper, array which has the same length as data, or list of them. Keys to group by on the pivot table column. If an array is passed, it is being used as the same manner as column values.
aggfunc: function to use for aggregation, defaulting to numpy.mean
"""


print("\n多级数据透视表")
age = pd.cut(titanic['age'], [0, 18, 80])#用pd.cut函数将年龄分段
print("\n年龄分组\n",age.head())
print(titanic.pivot_table('survived', ['sex', age], 'class'))#默认应用参数是aggfunc='mean'
"""根据函数源代码和参数,因为函数直接作用在对象上,所以这里的values='survived',
等同pd.pivot_table(titanic,'survived'...)"""

#cut将根据值本身来选择间隔,
#qcut是根据这些值的频率来选择均匀间隔,即每个间隔数的数量是相同的
fare = pd.qcut(titanic['fare'], 2)#根据各种价格数量,将价格区间分割成数量相同的两部分
print("\n分隔票价\n",fare.head())
print("\n四维累计数据表\n",titanic.pivot_table('survived', ['sex', age], [fare, 'class']))

print("\n其他数据透视表选项")
"""Pandas 0.18版的函数签名
DataFrame.pivot_table(data, values=None, index=None, columns=None,
aggfunc='mean', fill_value=None, margins=False,dropna=True, margins_name='All')

fill_value 和dropna 这两个参数用于处理缺失值

aggfunc 参数用于设置累计函数类型,默认值是均值(mean)GroupBy 的用法一样,累
计函数可以用一些常见的字符串('sum'、'mean'、'count'、'min'、'max' 等)表示,也
可以用标准的累计函数(np.sum()、min()、sum() 等)表示
"""
#还可以通过字典为不同的列指定不同的累计函数:
a=titanic.pivot_table(index='sex', columns='class',
                      aggfunc={'survived':sum, 'fare':'mean'})
print(a)
#注意这里没有提供values参数,因为为aggfunc设置指定映射关系是,待透视的数据就已经确定下来了。

#当需要计算每一组的总数时,可以通过margins 参数来设置:
a=titanic.pivot_table('survived', index='sex', columns='class', margins=True)
print("\n添加总数计算:\n",a)
# margin的标签可以通过margins_name 参数进行自定义,默认值是"All"


print("\n案例:美国人的生日")
births = pd.read_csv('births.csv')#公开生日数据
print(births.head())

births['decade'] = 10 * (births['year'] // 10)#提取每10年为单位的统计数据
a=births.pivot_table('births', index='decade', columns='gender', aggfunc='sum')
print("\n每10年出生男女人口\n",a)

quartiles = np.percentile(births['births'], [25, 50, 75])#np笔记有记录percentile函数
print(quartiles)
mu = quartiles[1]
sig = 0.74 * (quartiles[2] - quartiles[0])
#看不懂这个案例下面就不记录了


print("\n3.11 向量化字符串操作")
#由于NumPy并没有为字符串数组提供简单的接口,因此需要通过繁琐的for循环来解决问题
data = ['peter', 'Paul', 'MARY', 'gUIDO']
print([s.capitalize() for s in data])#capitalize()将开头字母改成大写其他为小写
#出现缺失值会导致出错

data = ['peter', 'Paul', None, 'MARY', 'gUIDO']
names = pd.Series(data)
print(names)
print("\npandas字符串操作\n",names.str.capitalize())#在pd对象直接用str属性可以量化操作
#Pandas 为包含字符串的Series 和Index 对象提供的str属性,来进行向量化字符串操作同时跳过缺失值

print("\nPandas字符串方法列表")
#几乎所有Python 内置的字符串方法都被复制到Pandas 的向量化字符串方法中。
"""
下面的表格列举了Pandas 的str 方法借鉴Python 字符串方法的内容:
len()     lower()       translate()     islower()
ljust()   upper()       startswith()    isupper()
rjust()   find()        endswith()      isnumeric()
center()  rfind()       isalnum()       isdecimal()
zfill()   index()       isalpha()       split()
strip()   rindex()      isdigit()       rsplit()
rstrip()  capitalize()  isspace()       partition()
lstrip()  swapcase()    istitle()       rpartition()
"""
monte = pd.Series(['Graham Chapman', 'John Cleese', 'Terry Gilliam',
                   'Eric Idle', 'Terry Jones', 'Michael Palin'])

#这些方法的返回值不同,例如lower() 方法返回一个字符串Series:
print(monte.str.lower())
print(monte.str.len())#返回数值
print("\n返回布尔值\n",monte.str.startswith('T'))#判断开头是否是大写T
print("\n返回复合值\n",monte.str.split())#分割字符串
#!!!!想要量化字符串操作 有多少步就要有多少个.str 例:.str.split('-').str[1].str[0:-1]

#下面方法支持正则表达式处理每个字符串元素
"""
表3-4:Pandas向量化字符串方法与Python标准库的re模块函数的对应关系
方法                  描述
match()               对每个元素调用re.match(),返回布尔类型值
extract()             对每个元素调用re.match(),返回匹配的字符串组(groups)
findall()             对每个元素调用re.findall()
replace()             用正则模式替换字符串
contains()            对每个元素调用re.search(),返回布尔类型值
count()               计算符合正则模式的字符串的数量
split()               等价于str.split(),支持正则表达式
rsplit()              等价于str.rsplit(),支持正则表达式
"""

print(monte.str.extract('([A-Za-z]+)'))#用正则表达式可以提取元素前面的连续字母作为名字
print(monte.str.findall(r'^[^AEIOU].*[^aeiou]$'))#找出所有开头和结尾都是辅音字母的名字

print("\n其他字符串方法")
"""
表3-5 其他Pandas字符串方法
方法             描述
get()            获取元素索引位置上的值,索引从0 开始
slice()          对元素进行切片取值
slice_replace()  对元素进行切片替换
cat()            连接字符串(此功能比较复杂,建议阅读文档)
repeat()         重复元素
normalize()      将字符串转换为Unicode 规范形式
pad()            在字符串的左边、右边或两边增加空格
wrap()           将字符串按照指定的宽度换行
join()           用分隔符连接Series 的每个元素
get_dummies     按照分隔符提取每个元素的dummy 变量,转换为独热(one-hot)编码的DataFrame
"""

#向量化字符串的取值与切片操作df.str.slice(0, 3) 等价于df.str[0:3]:
#df.str.get(i) 与df.str[i] 的按索引取值效果类似。
print(monte.str.split().str.get(-1))#留意两个str

print("\n指标变量")

full_monte = pd.DataFrame({'name': monte,
                           'info': ['B|不是吧|D', 'B|D', 'A|C', 'B|D', 'B|C','B|C|D']})
print(full_monte)
#get_dummies()方法可以让你快速将这些指标变量分割成一个独热编码的DataFrame(每个元素都是0或1)
a=full_monte['info'].str.get_dummies('|')
print(a)


print("\nb3.12 处理时间序列")
"""
本节将介绍的日期与时间数据主要包含三类:
1.时间戳表示某个具体的时间点(例如2015 年7 月4 日上午7 点)。
2.时间间隔与周期表示开始时间点与结束时间点之间的时间长度,
  周期通常是指一种特殊形式的时间间隔,每个间隔长度相同,彼此之间不会重叠
3.时间增量(time delta)或持续时间(duration)表示精确的时间长度(某程序运行持续时间22.56秒)
"""

print("Python的日期与时间工具:")
#1. 原生Python的日期与时间工具:datetime与dateutil
from datetime import datetime
print(datetime(year=2015, month=7, day=4))#打印和输出是两种形式

#或者使用dateutil 模块对各种字符串格式的日期进行正确解析:
from dateutil import parser
date = parser.parse("4th of July, 2015")
print(date)#不打印,返回的是datetime 对象:datetime.datetime(2015, 7, 4, 0, 0)(上面一样)

#一旦有了datetime 对象,就可以进行许多操作了,例如打印出这一天是星期几:
print(date.strftime('%A'))


print("\n2. 时间类型数组:NumPy的datetime64类型")
date = np.array('2015-07-04', dtype=np.datetime64)#传入dtype=np.datetime64类型
print(date)#out:array(datetime.date(2015, 7, 4), dtype='datetime64[D]')

print(date + np.arange(12))#只要有了这个日期格式,就可以进行快速的向量化运算

#以天为单位的日期:
print(np.datetime64('2015-07-04'))#out:numpy.datetime64('2015-07-04')
#以分钟为单位的日期:
print(np.datetime64('2015-07-04 12:00'))#out:numpy.datetime64('2015-07-04T12:00')

#时区将自动设置为执行代码的操作系统的当地时区。可以通过各种格式的代码设置基本时间单位:
print(np.datetime64('2015-07-04 12:59:59.50', 'M'))#纳秒

"""
表3-6:日期与时间单位格式代码
代码    含义              时间跨度 (相对) 时间跨度 (绝对)
Y       年(year)         ± 9.2e18 年[9.2e18 BC, 9.2e18 AD]
M       月(month)        ± 7.6e17 年[7.6e17 BC, 7.6e17 AD]
W       周(week)         ± 1.7e17 年[1.7e17 BC, 1.7e17 AD]
D       日(day)          ± 2.5e16 年[2.5e16 BC, 2.5e16 AD]
h       时(hour)         ± 1.0e15 年[1.0e15 BC, 1.0e15 AD]
m       分(minute)       ± 1.7e13 年[1.7e13 BC, 1.7e13 AD]
s       秒(second)       ± 2.9e12 年[ 2.9e9 BC, 2.9e9 AD]
ms      毫秒(millisecond) ± 2.9e9 年[ 2.9e6 BC, 2.9e6 AD]
下面还有,不列出来P168

虽然datetime64 弥补了Python 原生的datetime 类型的不足,但它
缺少了许多datetime(尤其是dateutil)原本具备的便捷方法与函数,
"""

print("\n3. Pandas的日期与时间工具:理想与现实的最佳解决方案")

date = pd.to_datetime("4th of July, 2015")#可以尝试多种时间格式,函数会自动识别
print(date)
print(date.strftime('%A'))

#也可以直接进行NumPy 类型的向量化运算:
a=date + pd.to_timedelta(np.arange(12), 'D')
print(a)


print("Pandas时间序列:用时间作索引")
#我们可以通过一个时间索引数据创建一个Series 对象:
index = pd.DatetimeIndex(['2014-07-04', '2015-07-04', '2015-08-04','2014-08-04'])
data = pd.Series([0, 1, 2, 3], index=index)
print(data)

print(data['2014-07-04':'2015-07-04'])#直接用日期进行切片取值
#是直接根据日期来切片筛选数据,只是日期没有排序

#直接通过年份切片获取该年的数据:(仅在此类Series 上可用的取值操作)
print("\n通过年份切片\n",data['2015'])


print("\nPandas时间序列数据结构")#P170
"""
• 针对时间戳数据,Pandas 提供了Timestamp 类型。与前面介绍的一样,它本质上是
Python 的原生datetime 类型的替代品,但是在性能更好的numpy.datetime64 类型的基
础上创建。对应的索引数据结构是DatetimeIndex。
• 针对时间周期数据,Pandas 提供了Period 类型。这是利用numpy.datetime64 类型将固
定频率的时间间隔进行编码。对应的索引数据结构是PeriodIndex。
• 针对时间增量或持续时间,Pandas 提供了Timedelta 类型。Timedelta 是一种代替Python
原生datetime.timedelta 类型的高性能数据结构,同样是基于numpy.timedelta64 类型。
对应的索引数据结构是TimedeltaIndex。
"""
#对pd.to_datetime()传递一个日期会返回一个Timestamp类型,传递一个时间序列会返回一个DatetimeIndex类型:
dates = pd.to_datetime([datetime(2015, 7, 3), '4th of July, 2015',
                        '2015-Jul-6', '07-07-2015', '20150708'])#以解析许多日期与时间格式
#传入时间序列返回一个DatetimeIndex类型

#下面为时间数据3种类型事例,对概念比较容易理解
print(dates)
#最基础的日期/时间对象是Timestamp 和DatetimeIndex。这两种对象可以直接使用,最常用
#的方法是pd.to_datetime() 函数,

#任何DatetimeIndex 类型都可以通过to_period() 方法和一个频率代码转换成PeriodIndex类型。
print(dates.to_period('D'))#用'D' 将数据转换成单日的时间序列

#当用一个日期减去另一个日期时,返回的结果是TimedeltaIndex 类型:
print(dates - dates[0])

print("\n有规律的时间序列:pd.date_range()")
"""Pandas 提供了一些方法:pd.date_range() 可以处理时间戳、pd.period_range() 
可以处理周期、pd.timedelta_range() 可以处理时间间隔"""

#通过开始日期、结束日期和频率代码(同样是可选的)创建一个有规律的日期序列,默认的频率是天:
a=pd.date_range('2015-07-03', '2015-07-10')#生成一个有规律的日期序列
print(a)
#pd.date_range(start=None,end=None,periods=None,freq=None,tz=None,normalize=False,
#               name=None,closed=None,**kwargs)

#日期范围不一定非是开始时间与结束时间,也可以是开始时间与周期数periods:
print("\n时间+周期数\n",pd.date_range('2015-07-03', periods=8))

#可以通过freq 参数改变时间间隔,默认值是D
print("\n修改时间间隔\n",pd.date_range('2015-07-03', periods=8, freq='H'))
#以上三个栗子都是生成日期序列(时间戳),即DatetimeIndex类型对象

#创建一个有规律的周期或时间间隔序列,有类似的函数pd.period_range() 和pd.timedelta_range()。
b=pd.period_range('2015-07', periods=8, freq='M')#
print("\n周期序列\n",b)#生成PeriodIndex类型序列
# pd.period_range(start=None, end=None, periods=None, freq=None, name=None)

#一个以小时递增的序列:
c=pd.timedelta_range(0, periods=10, freq='H')
print("\n时间间隔序列序列\n",c)#生成PeriodIndex类型序列
#pd.timedelta_range(start=None,end=None,periods=None,freq=None,name=None,closed=None)


print("\n时间频率与偏移量")
"""
Pandas 时间序列工具的基础是时间频率或偏移量(offset)代码:
代码  描述                                    代码    描述
D     天(calendar day,按日历算,含双休日)   B      天(business day,仅含工作日)
W     周(weekly)
M     月末(month end)                       BM     月末(business month end,仅含工作日)
Q     季末(quarter end)                     BQ     季末(business quarter end,仅含工作日)
A     年末(year end)                        BA     年末(business year end,仅含工作日)
H     小时(hours)                           BH     小时(business hours,工作时间)
T     分钟(minutes)
S     秒(seconds)
L     毫秒(milliseonds)
U     微秒(microseconds)
N     纳秒(nanoseconds)

月、季、年频率都是具体周期的结束时间(月末、季末、年末),而有一些以S(start,开
始)为后缀的代码表示日期开始:

表3-8:带开始索引的频率代码
代码      频率
MS        月初(month start)
BMS       月初(business month start,仅含工作日)
QS        季初(quarter start)
BQS       季初(business quarter start,仅含工作日)
AS        年初(year start)
BAS       年初(business year start,仅含工作日)
"""

#可以将频率组合起来创建的新的周期:用小时(H)和分钟(T)的组合来实现2 小时30 分钟
a=pd.timedelta_range(0, periods=9, freq="2H30T")
print(a)

"""所有这些频率代码都对应Pandas 时间序列的偏移量,具体内容可以在pd.tseries.offsets
模块中找到。例如,可以用下面的方法直接创建一个工作日偏移序列:"""
from pandas.tseries.offsets import BDay
a=pd.date_range('2015-07-01', periods=9, freq=BDay())
print(a)


print("\n3.13 高性能Pandas:eval()与query()")
#pandas虽然运算速度快但因为经常要创建临时中间对象,导致占用大量计算时间和内存

print("query()与eval()的设计动机:复合代数式")

#NumPy 与Pandas 都支持快速的向量化运算,下面对两个例子求和:
rng = np.random.RandomState(42)
x = rng.rand(10000000)
y = rng.rand(10000000)
start = time.clock()
a=x + y
end = time.clock()
print("使用numpu耗时\n",end-start)

#使用python的循环也可以计算,但速度要慢得多
start = time.clock()
np.fromiter((xi + yi for xi, yi in zip(x, y)),dtype=x.dtype, count=len(x))
end = time.clock()
print("\n使用python循环耗时:\n",end-start)

#但是numpy的这种向量化运算在处理复合代数式时效率比较低:
mask = (x > 0.5) & (y < 0.5)#复合代数式。。。

#等价于下面过程,每段中间过程都需要显式地分配内存。
#如果x 数组和y 数组非常大,运算就会占用大量的时间和内存消耗
tmp1 = (x > 0.5)
tmp2 = (y < 0.5)
mask = tmp1 & tmp2

#Numexpr 程序库可以让你在不为中间过程分配全部内存的前提下,完成元素到元素的复合代数式运算
import numexpr#np.allclose()可用于对比两种运算得出的结果是否一样
mask_numexpr = numexpr.evaluate('(x > 0.5) & (y < 0.5)')
print(np.allclose(mask, mask_numexpr))#np.allclose 比较两个array是不是每一元素都相等
#Numexpr在计算代数式时不需要为临时数组分配全部内存,计算比NumPy 更高效,尤其适合处理大型数组


print("\n用pandas.eval()实现高性能运算")
#Pandas 的eval() 函数用字符串代数式实现了DataFrame 的高性能运算
nrows, ncols = 100000, 100
rng = np.random.RandomState(42)#下面创建100000行100列0-1的随机数的dataframe
df1, df2, df3, df4 = (pd.DataFrame(rng.rand(nrows, ncols))for i in range(4))

start = time.clock()
df1 + df2 + df3 + df4
end = time.clock()
print("\n常规pandas耗时:\n",end-start)

#也可以通过pd.eval 和字符串代数式计算并得出相同的结果:
start = time.clock()
pd.eval('df1 + df2 + df3 + df4')
end = time.clock()
print("\npandas.eval()耗时:\n",end-start)


#pd.eval()支持的运算
df1, df2, df3, df4, df5 = (pd.DataFrame(rng.randint(0, 1000, (100, 3)))for i in range(5))

print("\n1.算术运算符")#pd.eval() 支持所有的算术运算符
result1 = -df1 * df2 / (df3 + df4) - df5
result2 = pd.eval('-df1 * df2 / (df3 + df4) - df5')
print(np.allclose(result1, result2))

print("\n2.比较运算符")#pd.eval() 支持所有的比较运算符,包括链式代数式
result1 = (df1 < df2) & (df2 <= df3) & (df3 != df4)
#比较式这个生成布尔值,为每项所在位置满足全部条件,然后该位置为ture
result2 = pd.eval('df1 < df2 <= df3 != df4')#注意与上面不一样


print("\n3.位运算符")#pd.eval() 支持&(与)和|(或)等位运算符
result1 = (df1 < 0.5) & (df2 < 0.5) | (df3 < df4)
result2 = pd.eval('(df1 < 0.5) & (df2 < 0.5) | (df3 < df4)')
result3 = pd.eval('(df1 < 0.5) and (df2 < 0.5) or (df3 < df4)')#或用and和or
print(np.allclose(result1, result2))


print("\n4.对象属性与索引")
#pd.eval() 可以通过obj.attr 语法获取对象属性,通过obj[index] 语法获取对象索引
result1 = df2.T[0] + df3.iloc[1]
result2 = pd.eval('df2.T[0] + df3.iloc[1]')

"""其他运算。目前pd.eval() 还不支持函数调用、条件语句、循环以及更复杂的运算。如
果你想要进行这些运算,可以借助Numexpr 来实现。"""


print("\n用DataFrame.eval()实现列间运算")#是DF的eval()方法,作用在df对象上,不是pd.eval()!!
#由于pd.eval() 是Pandas 的顶层函数,因此DataFrame 有一个eval() 方法可以做类似的运算
#使用eval() 方法的好处是可以借助列名称进行运算
df = pd.DataFrame(rng.rand(1000, 3), columns=['A', 'B', 'C'])
print(df.head())

#如果用前面介绍的pd.eval(),就可以通过下面的代数式计算这三列
result1 = (df['A'] + df['B']) / (df['C'] - 1)
result2 = pd.eval("(df.A + df.B) / (df.C - 1)")#还能这么用
print(np.allclose(result1, result2))

#DataFrame.eval() 方法可以通过列名称实现简洁的代数式:
result3 = df.eval('(A + B) / (C - 1)')#eval方法直接作用在dataframe对象上
print(np.allclose(result1, result3))

print("\n用DataFrame.eval()新增列")
#可以用df.eval() 创建一个新的列'D',然后赋给它其他列计算的值:
df.eval('D = (A + B) / C', inplace=True)#inplace=True 直接修改原值
print(df.head())
df.eval('D = (A - B) / C', inplace=True)#可以修改已有的列
print(df.head())


print("\n2. DataFrame.eval()使用局部变量")
#DataFrame.eval() 方法还支持通过@ 符号使用Python 的局部变量
column_mean = df.mean(1)
result1 = df['A'] + column_mean
result2 = df.eval('A + @column_mean')#!!!!!!!!!!!!
print(np.allclose(result1, result2))
#!!!!!!!!!!!!!@ 符号表示“这是一个变量名称而不是一个列名称”,
#  @ 符号只能在DataFrame.eval() 方法中使用,而不能在pandas.eval() 函数中使用,


print("\nDataFrame.query()方法")#DataFrame.query()适用于过滤运算
#DataFrame 基于字符串代数式的运算实现了另一个方法,被称为query()
result1 = df[(df.A < 0.5) & (df.B < 0.5)]#提取满足条件的行数据
result2 = pd.eval('df[(df.A < 0.5) & (df.B < 0.5)]')
np.allclose(result1, result2)
print(result2)
"""
和前面介绍过的DataFrame.eval() 一样,这是一个用DataFrame 列创建的代数式,但是不
能用DataFrame.eval()(因为你要的结果是包含DataFrame 的全部列)。不过,对于这种过滤运算,
你可以用query() 方法
"""
result2 = df.query('A < 0.5 and B < 0.5')
print(result2)
#query() 方法也支持用@ 符号引用局部变量:
Cmean = df['C'].mean()
result1 = df[(df.A < Cmean) & (df.B < Cmean)]
result2 = df.query('A < @Cmean and B < @Cmean')
print(np.allclose(result1, result2))


print("\n性能决定使用时机")
"""
在考虑要不要用这两个函数时,需要考虑计算时间和内存消耗,而内存消耗是更重要的影响
因素每个涉及NumPy 数组或Pandas 的DataFrame的复合代数式都会产生临时数组,
例如
In[26]: x = df[(df.A < 0.5) & (df.B < 0.5)]

它基本等价于:
In[27]: tmp1 = df.A < 0.5
tmp2 = df.B < 0.5
tmp3 = tmp1 & tmp2
x = df[tmp3]

df.values.nbytes可以大概估算一下变量的内存消耗

"""


# ----------------------------后续补充--------------------------------
# pandas获取groupby分组里最大值所在的行方法:
df = pd.DataFrame({'Sp':['a','b','c','d','e','f'], 
                  'Mt':['s1', 's1', 's2','s2','s2','s3'], 'Value':[1,2,3,4,5,6],
                  'Count':[3,2,5,10,10,6]})
print(df)

# 方法1:在分组中过滤出Count最大的行(这种效率非常低,不推荐使用)
df.groupby('Mt').apply(lambda t: t[t.Count==t.Count.max()])

# 方法2:idmax(旧版本pandas是argmax)(推荐)
print(df.iloc[df.groupby(['Mt']).apply(lambda x: x['Count'].idxmax())])

# 方法3:先排好序,然后每组取第一个(此方法效率非常高)
df.sort_values('Count', ascending=False).groupby('Mt', as_index=False).first()
# 最后一个的sort方法好像现在已经变成sort_value()方法了(没验证)


# pandas删除指定列中有空值的行
# mydf.dropna(subset=['列名'],inplace=True)

# dataframe在删除原来的行后索引不会随着删除而改变会保持原值,想要重置索引可以:
df = df.reset_index(drop=True)

# 获取当前时间
from datetime import datetime
nowTime=datetime.now().strftime('%Y%m%dt%H_%M_%S') 

# 替换表头(列名)
pd.columns = ['xx','yy','zz'] #不知道为什么失败了
a.rename(columns={'A':'a', 'B':'b', 'C':'c'}, inplace = True)#  第二种方法可以自定义修改个数

df = pd.read_clipboard() # 从粘贴板上读取数据

df.drop_duplicates(keep='first') # 去重


#pd.dtypes 查看每列的数据类型

 

你可能感兴趣的