写点什么

27. Pandas

作者:茶桁
  • 2023-08-23
    上海
  • 本文字数:24971 字

    阅读完需:约 82 分钟

27. Pandas

Hi, 大家好。我是茶桁。


先跟小伙伴们打个招呼,今天这节课呢,就是我们 Python 基础课程的最后一节课了。


在本节课之前,我们讲解了 Python 的基础,介绍了两个第三方库。而今天一样会介绍一个第三方库:Pandas。


虽然是最后一节课了,但是本节课的任务却是一点也不轻松。相比较而言,如果你以后从事的是数据治理和分析工作,那么本节课的内容可能会是你在今后工作中用到的最多的内容。我们需要学习行列索引的操作,数据的处理,数据的合并,多层索引,时间序列,数据的分组聚合(重点)。最后,我们会有一个案例的展示。


听着是不是很兴奋?那我们就开始吧。


在开始讲解 pandas 之前,我们讲解一些关于数据的小知识。


我们大部分人应该都用过 Excel 表格,而我们从数据库中拿到的数据,也基本上和 Excel 上的数据差不多,都是由行列组成的。可以直接导出为csv文件。


也就是说,我们大部分时候要处理的数据,基本上都一组二维数据。例如,我们今天最后案例要用到的一个电影数据(部分),如图:



这里面,我们就将数据通过行列来展示和定位。


了解了这一点之后,我们来开始学习 pandas。

Pandas 简介


在之前的介绍中,我们发现很多的操作似乎都似曾相识,在 NumPy 中我们好像都接触过。


有这种感觉很正常,Pandas 本身就是基于 NumPy 的一种工具,该⼯具是为了解决数据分析任务⽽创建的。Pandas 纳⼊了⼤量库和⼀些标准的数据模型,提供了⾼效地操作⼤型数据集所需的⼯具。pandas 提供了⼤量能使我们快速 便捷地处理数据的函数和⽅法。

Series 对象

Pandas 基于两种数据类型:seriesdataframe


Series是 Pandas 中最基本的对象,Series 类似⼀种⼀维数组。事实上,Series 基本上就是基于 NumPy 的数组对象来的。和 NumPy 的数组不同,Series 能为数据⾃定义标签,也就是索引(index),然后 通过索引来访问数组中的数据。


Dataframe是⼀个⼆维的表结构。Pandas 的 dataframe 可以存储许多种不同的数据类型,并且每⼀个坐标轴都有⾃⼰的标签。你可以把它想象成⼀个 series 的字典项。


在开始基于代码进行学习之前,我们需要应用一些必要项。


import numpy as npimport pandas as pdfrom pandas import Seriesfrom pandas import DataFrame as df
复制代码


现在我们来看看 Series 的一些基本操作:


创建 Series 对象并省略索引


index 参数是可省略的,你可以选择不输⼊这个参数。如果不带 index 参数,Pandas 会⾃动⽤默认 index 进⾏索引,类似数组,索引值是 [0, ..., len(data) - 1]


sel = Series([1,2,3,4])print(sel)
---0 11 22 33 4dtype: int64
复制代码


我们之前在 NumPy 中学习了 dtype, 以及它的相关数据类型。所以现在的dtype: int64我们应该能理解是什么意思了。


我们看打印的结果,在1,2,3,4前面,是 Series 默认生成的索引值。


通常我们会⾃⼰创建索引


sel = Series(data=[1,2,3,4], index= list('abcd'))print(sel)
---a 1b 2c 3d 4dtype: int64
复制代码


这个时候,我们可以对这个 Series 对象操作分别获取内容和索引


print(f'values: {sel.values}')print(sel.index)
---values: [1 2 3 4]Index(['a', 'b', 'c', 'd'], dtype='object')
复制代码


又或者,我们可以直接获取健值对(索引和值对)。


print(list(sel.iteritems()))
---[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
复制代码


那么这种健值对的形式让你想到了什么?是字典对吧?


我们完全可以将字典转为 Series:


dict={"red":100,"black":400,"green":300,"pink":900}se3=Series(dict) print(se3)
---red 100black 400green 300pink 900dtype: int64
复制代码


Series 数据获取


在 Series 拿到数据转为 Series 对象之后,诺大的数据中,我们如何定位并获取到我们想要的内容呢?


Series 在获取数据上,支持位置、标签、获取不连续数据,使用切片等方式。我们一个个的看一下:


Series 对象同时⽀持位置和标签两种⽅式获取数据


print('索引下标',sel['c'])print('位置下标',sel[2])
---索引下标 3位置下标 3
复制代码


获取不连续的数据


print('位置切⽚\n',sel[1:3])# 左包含右不包含print('\n索引切⽚\n',sel['b':'d'])# 左右都包含
---位置切⽚b 2c 3dtype: int64
索引切⽚b 2c 3d 4dtype: int64
复制代码


我们看到结果,发现两组数据数量不对,但是其实我们获取的位置都是一样的。这是因为,位置切片的方式会是「左包含右不包含」的,而索引切片则是「左右都包含」。


重新赋值索引的值


除了获取数据之外,我们还可以对数据进行重新索引。其实在之前我们将索引的时候,第二种自己赋值的方式实际上就是一个重新赋值了,将自己定义的值替换了默认值。这里让我们再来看一下:


sel.index = list('dcba')print(sel)
---d 1c 2b 3a 4dtype: int64
复制代码


还有一种重新索引的方法reindex, 这会返回一个新的 Series。调用reindex将会重新排序,缺失值这会用NaN填补。


print(sel.reindex(['b','a','c','d','e']))
---b 3.0a 4.0c 2.0d 1.0e NaNdtype: float64
复制代码


在重新索引的时候,我们特意多增加了一个索引。在最后一位没有数据的情况下,缺失值被NaN填补上了。


丢弃指定轴上的项


sel = pd.Series(range(10, 15))print(sel)print(sel.drop([2,3]))
---0 101 112 123 134 14dtype: int640 101 114 14dtype: int64
复制代码


使用drop,会丢弃掉轴上的项,例子中,我们将 2,3 进行了丢弃。


Series 进⾏算术运算操作


对 Series 的算术运算都是基于 index 进⾏的。我们可以⽤加减乘除(+ - * /)这样的运算符对两个 Series 进⾏运算,Pandas 将会根据索引 index,对响应的数据进⾏计算,结果将会以浮点数的形式存储,以避免丢失精度。如果 Pandas 在两个 Series ⾥找不到相同的 index,对应的位置就返回⼀个空值 NaN


# Series 算数运算series1 = pd.Series([1,2,3,4],['London','HongKong','Humbai','lagos'])series2 = pd.Series([1,3,6,4],['London','Accra','lagos','Delhi'])
print('series1-series2:')print(series1-series2) print('\nseries1+series2:')print(series1+series2) print('\nseries1*series2:')print(series1*series2)
---series1-series2:Accra NaNDelhi NaNHongKong NaNHumbai NaNLondon 0.0lagos -2.0dtype: float64
series1+series2:Accra NaNDelhi NaNHongKong NaNHumbai NaNLondon 2.0lagos 10.0dtype: float64
series1*series2:Accra NaNDelhi NaNHongKong NaNHumbai NaNLondon 1.0lagos 24.0dtype: float64
复制代码


除此之外,Series 的算术运算操作同样也支持 NumPy 的数组运算


sel = Series(data = [1,6,3,5], index = list('abcd'))print(sel[sel>3]) # 布尔数组过滤print(sel*2) # 标量乘法print(np.square(sel)) # 可以直接加⼊到numpy的数学函数
---b 6d 5dtype: int64a 2b 12c 6d 10dtype: int64a 1b 36c 9d 25dtype: int64
复制代码

DataFrame

DataFrame(数据表)是⼀种 2 维数据结构,数据以表格的形式存储,分成若⼲⾏和列。通过 DataFrame,你能很⽅便地处理数据。常见的操作⽐如选取、替换⾏或列的数据,还能重组数据表、修改索引、多重筛选等。我们基本上可以把 DataFrame 理解成⼀组采⽤同样索引的 Series 的集合。调⽤ DataFrame()可以将多种格式的数据转换为 DataFrame 对象,它的的三个参数dataindexcolumns 分别为数据、⾏索引和列索引。

DataFrame 的创建

我们可以使用二维数组


df1 = df(np.random.randint(0,10,(4,4)),index=[1,2,3,4],columns=['a','b','c','d']) print(df1)
--- a b c d1 9 6 3 02 6 2 7 03 3 1 6 54 6 6 7 1
复制代码


也可以使用字典创建


行索引由 index 决定,列索引由字典的键决定


dict={'Province': ['Guangdong', 'Beijing', 'Qinghai','Fujian'],'pop': [1.3, 2.5, 1.1, 0.7], 'year': [2022, 2022, 2022, 2022]}df2= df(dict,index=[1,2,3,4]) print(df2)
--- Province pop year1 Guangdong 1.3 20222 Beijing 2.5 20223 Qinghai 1.1 20224 Fujian 0.7 2022
复制代码


使用from_dict


dict2={"a":[1,2,3],"b":[4,5,6]}df6=df.from_dict(dict2) print(df6)
--- a b0 1 41 2 52 3 6
复制代码


索引相同的情况下,相同索引的值会相对应,缺少的值会添加NaN


data = { 'Name':pd.Series(['zs','ls','we'],index=['a','b','c']), 'Age':pd.Series(['10','20','30','40'],index=['a','b','c','d']), 'country':pd.Series(['中国','⽇本','韩国'],index=['a','c','b'])}df = pd.DataFrame(data)print(df)
--- Name Age countrya zs 10 中国b ls 20 韩国c we 30 ⽇本d NaN 40 NaN
复制代码


看了那么多 DataFrame 的转换方式,那我们如何将数据转为字典呢?DataFrame 有一个内置方法to_dict()能将 DataFrame 对象转换为字典:


dict = df.to_dict()print(dict)
---{'Name': {'a': 'zs', 'b': 'ls', 'c': 'we', 'd': nan}, 'Age': {'a': '10', 'b': '20', 'c': '30', 'd': '40'}, 'country': {'a': '中国', 'b': '韩国', 'c': '⽇本', 'd': nan}}
复制代码

DataFrame 对象常⽤属性

让我们先来生成一组数据备用:


df_dict = {'name':['James','Curry','Iversion'],'age':['18','20','19'], 'national':['us','China','us'] }df = pd.DataFrame(data=df_dict,index=['0','1','2'])print(df)
--- name age national0 James 18 us1 Curry 20 China2 Iversion 19 us
复制代码


获取⾏数和列数


print(df.shape)
---(3,3)
复制代码


获取⾏索引


print(df.index.tolist())
---['0', '1', '2']
复制代码


获取列索引


print(df.columns.tolist())
---['name', 'age', 'national']
复制代码


获取数据的类型


print(df.dtypes)
---name objectage objectnational objectdtype: object
复制代码


获取数据的维度


print(df.ndim)
---2
复制代码


values 属性也会以⼆维 ndarray 的形式返回 DataFrame 的数据


print(df.values)
---[['James' '18' 'us'] ['Curry' '20' 'China'] ['Iversion' '19' 'us']]
复制代码


展示 df 的概览


print(df.info())
---<class 'pandas.core.frame.DataFrame'>Index: 3 entries, 0 to 2Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 3 non-null object 1 age 3 non-null object 2 national 3 non-null objectdtypes: object(3)memory usage: 96.0+ bytesNone
复制代码


显示头⼏⾏,默认显示 5⾏


print(df.head(2))
--- name age national0 James 18 us1 Curry 20 China
复制代码


显示后⼏⾏


print(df.tail(1))
--- name age national2 Iversion 19 us
复制代码


获取 DataFrame 的列


print(df['name'])
---0 James1 Curry2 IversionName: name, dtype: object
复制代码


因为我们只获取⼀列,所以返回的就是⼀个 Series


print(type(df['name']))
---<class 'pandas.core.series.Series'>
复制代码


如果获取多个列,那返回的就是⼀个 DataFrame 类型:


print(df[['name','age']])print(type(df[['name','age']]))
--- name age0 James 181 Curry 202 Iversion 19<class 'pandas.core.frame.DataFrame'>
复制代码


获取一行


print(df[0:1])
--- name age national0 James 18 us
复制代码


去多⾏


print(df[1:3])
--- name age national1 Curry 20 China2 Iversion 19 us
复制代码


取多⾏⾥⾯的某⼀列(不能进⾏多⾏多列的选择)


print(df[1:3][['name','age']])
--- name age1 Curry 202 Iversion 19
复制代码


⚠️ 注意:df[]只能进⾏⾏选择,或列选择,不能同时多⾏多列选择。比如在 NumPy 中的data[:,1:3]这种是不行的。当然,并不是没有办法获取,我们接着往下看:


df.loc通过标签索引⾏数据;df.iloc通过位置获取⾏数据


获取某⼀⾏某⼀列的数据


print(df.loc['0','name'])
---James
复制代码


⼀⾏所有列


print(df.loc['0',:])
---name Jamesage 18national usName: 0, dtype: object
复制代码


某⼀⾏多列的数据


print(df.loc['0',['name','age']])
---name Jamesage 18Name: 0, dtype: object
复制代码


选择间隔的多⾏多列


print(df.loc[['0','2'],['name','national']]) 
--- name national0 James us2 Iversion us
复制代码


选择连续的多⾏和间隔的多列


print(df.loc['0':'2',['name','national']])
--- name national0 James us1 Curry China2 Iversion us
复制代码


取⼀⾏


print(df.iloc[1])
---name Curryage 20national ChinaName: 1, dtype: object
复制代码


取连续多⾏


print(df.iloc[0:2])
--- name age national0 James 18 us1 Curry 20 China
复制代码


取间断的多⾏


print(df.iloc[[0,2],:])
--- name age national0 James 18 us2 Iversion 19 us
复制代码


取某⼀列


print(df.iloc[:,1])
---0 181 202 19Name: age, dtype: object
复制代码


某⼀个值


print(df.iloc[1,0])
---Curry
复制代码


修改值


df.iloc[0,0]='panda'print(df)
--- name age national0 panda 18 us1 Curry 20 China2 Iversion 19 us
复制代码


dataframe 中的排序⽅法


df = df.sort_values(by='age', ascending*=False) print(df)
--- name age national1 Curry 20 China2 Iversion 19 us0 panda 18 us
复制代码


ascending=False是降序排列,默认为True,也就是升序。

dataframe 修改 index**、**columns

一样,让我们先创建一组新的数据供我们使用:


df1 = pd.DataFrame(np.arange(9).reshape(3, 3), index = ['bj', 'sh', 'gz'], columns=['a', 'b', 'c'])print(df1)
--- a b cbj 0 1 2sh 3 4 5gz 6 7 8
复制代码


修改 df1 的 index


df1.index可以打印出 df1 的索引值,同时也可以为其赋值。


print(df1.index) # 可以打印出print的值,同时也可以为其赋值df1.index = ['beijing', 'shanghai', 'guangzhou']print(df1)
---Index(['bj', 'sh', 'gz'], dtype='object') a b cbeijing 0 1 2shanghai 3 4 5guangzhou 6 7 8
复制代码


⾃定义 map 函数(x 是原有的⾏列值)


# ⾃定义map函数(x是原有的⾏列值) def test_map(x):    return x+'_ABC'print(df1.rename(index=test_map, columns=test_map, inplace=True))print(df1)
---None a_ABC b_ABC c_ABCbeijing_ABC 0 1 2shanghai_ABC 3 4 5guangzhou_ABC 6 7 8
复制代码


其中的inplace传入一个布尔值,默认为 False。指定是否返回新的 DataFrame。如果为 True,则在原 df 上修改, 返回值为 None。


rename 可以传⼊字典,为某个 index 单独修改名称


df3 = df1.rename(index={'beijing_ABC':'beijing'}, columns = {'a_ABC':'aa'})print(df3)
--- aa b_ABC c_ABCbeijing 0 1 2shanghai_ABC 3 4 5guangzhou_ABC 6 7 8
复制代码


列转化为索引


df1=pd.DataFrame({'X':range(5),'Y':range(5),'S':list("abcde"),'Z': [1,1,2,2,2]})print(df1)
--- X Y S Z0 0 0 a 11 1 1 b 12 2 2 c 23 3 3 d 24 4 4 e 2
复制代码


指定⼀列为索引 (drop=False 指定同时保留作为索引的列)


result = df1.set_index('S',drop=False)result.index.name=Noneprint(result)
--- X Y S Za 0 0 a 1b 1 1 b 1c 2 2 c 2d 3 3 d 2e 4 4 e 2
复制代码


⾏转为列索引


result = df1.set_axis(df1.iloc[0],axis=1,inplace=False)result.columns.name=Noneprint(result)
--- 0 0 a 10 0 0 a 11 1 1 b 12 2 2 c 23 3 3 d 24 4 4 e 2
复制代码

添加数据

先增加一组数据:


df1 = pd.DataFrame([['Snow','M',22],['Tyrion','M',32],['Sansa','F',18], ['Arya','F',14]],columns=['name','gender','age'])print(df1)
--- name gender age0 Snow M 221 Tyrion M 322 Sansa F 183 Arya F 14
复制代码


在数据框最后加上 score⼀列,注意增加列的元素个数要跟原数据列的个数⼀样。


df1['score']=[80,98,67,90] print(df1)
--- name gender age score0 Snow M 22 801 Tyrion M 32 982 Sansa F 18 673 Arya F 14 90
复制代码


在具体某个位置插⼊⼀列可以⽤insert的⽅法, 语法格式:列表.insert(index, obj)


index --->对象 obj 需要插⼊的索引位置。


obj ---> 要插⼊列表中的对象(列名)


将数据框的列名全部提取出来存放在列表⾥


col_name=df1.columns.tolist() print(col_name)
---['name', 'gender', 'age', 'score']
复制代码


在列索引为 2 的位置插⼊⼀列,列名为:city


col_name.insert(2,'city')print(col_name)
---['name', 'gender', 'city', 'age', 'score']
复制代码


刚插⼊时不会有值,整列都是 NaN,我们使用DataFrame.reindex()对原⾏/列索引重新构建索引值


df1=df1.reindex(columns=col_name)print(df1)
--- name gender city age score0 Snow M NaN 22 801 Tyrion M NaN 32 982 Sansa F NaN 18 673 Arya F NaN 14 90
复制代码


给 city 列赋值


df1['city']=['北京京','⼭⻄西','湖北北','澳⻔门'] print(df1)
--- name gender city age score0 Snow M 北京京 22 801 Tyrion M ⼭⻄西 32 982 Sansa F 湖北北 18 673 Arya F 澳⻔门 14 90
复制代码


df 中的insert是插⼊⼀列。语法和关键参数为:


df.insert(iloc,column,value)


iloc:要插⼊的位置


colunm:列名


value:值


刚才我们插入city列的时候省略了value,所以新建列值全部为NaN,这次我们加上再看:


df1.insert(2,'score2',[80,98,67,90]) print(df1)
--- name gender score2 city age score0 Snow M 80 北京京 22 801 Tyrion M 98 ⼭⻄西 32 982 Sansa F 67 湖北北 18 673 Arya F 90 澳⻔门 14 90
复制代码


插⼊⼀⾏


# 插⼊⼀⾏row=['111','222','333','444','555','666'] df1.iloc[1]=rowprint(df1)
--- name gender score2 city age score0 Snow M 80 北京京 22 801 111 222 333 444 555 6662 Sansa F 67 湖北北 18 673 Arya F 90 澳⻔门 14 90
复制代码


插入行的时候,列个数必须对应才可以,否则会报错。


目前这组数据已经被我们玩乱了,我们再重新生成一组数据来看后面的:


df1 = pd.DataFrame([['Snow','M',22],['Tyrion','M',32],['Sansa','F',18],['Arya','F',14]],columns=['name','gender','age'])print(df1)
--- name gender age0 Snow M 221 Tyrion M 322 Sansa F 183 Arya F 14
复制代码


再继续创建一组数据,我们将尝试将两组数据进行合并, 新创建的这组数据⽤来增加进数据框的最后⼀⾏。


new=pd.DataFrame({'name':'lisa','gender':'F','age':19},index=[0])print(new)
--- name gender age0 lisa F 19
复制代码


在原数据框 df1 最后⼀⾏新增⼀⾏,⽤append⽅法


df1=df1.append(new,ignore_index=True) print(df1)
--- name gender age0 Snow M 221 Tyrion M 322 Sansa F 183 Arya F 144 lisa F 19
复制代码


ignore_index=False,表示不按原来的索引, 从 0 开始⾃动递增。


objs:合并对象


axis:合并⽅式,默认 0 表示按列合并,1 表示按⾏合并


ignore_index:是否忽略索引


df1 = pd.DataFrame(np.arange(6).reshape(3,2),columns=['four','five'])df2 = pd.DataFrame(np.arange(6).reshape(2,3),columns=['one','two','three'])print(df1)print(df2)
--- four five0 0 11 2 32 4 5 one two three0 0 1 21 3 4 5
复制代码


按行合并


result = pd.concat([df1,df2],axis=1)print(result)
--- four five one two three0 0 1 0.0 1.0 2.01 2 3 3.0 4.0 5.02 4 5 NaN NaN NaN
复制代码


按列合并


   four  five  one  two  three0   0.0   1.0  NaN  NaN    NaN1   2.0   3.0  NaN  NaN    NaN2   4.0   5.0  NaN  NaN    NaN3   NaN   NaN  0.0  1.0    2.04   NaN   NaN  3.0  4.0    5.0
复制代码


看结果我们能看出来,在合并的时候,如果对应不到值,那么就会默认添加NaN值。


DataFrame 的删除


df2 = pd.DataFrame(np.arange(9).reshape(3,3),columns=['one','two','three'])print(df2)df3=df2.drop(['one'],axis=1, inplace=True)print(df2)print(df3)
--- one two three0 0 1 21 3 4 52 6 7 8 two three0 1 21 4 52 7 8None
复制代码


lables:要删除数据的标签


axis:0 表示删除⾏,1 表示删除列,默认 0


inplace:是否在当前 df 中执⾏此操作


最后的返回值为None,原因是我们设置inplaceTrue,在当前df中执行操作。如果我们将其设置为False,则会将操作后的值进行返回,生成一个新的对象。


df3=df2.drop([0,1],axis=0, inplace=False)print(df2)print(df3)
--- one two three0 0 1 21 3 4 52 6 7 8 one two three2 6 7 8
复制代码

数据处理

在我们查看完 DataFrame 的基础操作之后,我们现在来正式开始数据处理。


我们可以通过通过dropna()滤除缺失数据,先让我们创建一组数据:


se=pd.Series([4,NaN,8,NaN,5]) print(se)
---0 4.01 NaN2 8.03 NaN4 5.0dtype: float64
复制代码


尝试清除缺失数据,也就是NaN值:


print(se.dropna())
---0 4.02 8.04 5.0dtype: float64
复制代码


在清除数据之前,我们有两个方法可以判断当前数据中是否有缺失数据,不过这两个方法的判断方式是相反的,一个是判断不是缺失数据,一个判断是缺失数据:


print(se.notnull())print(se.isnull())
---0 True1 False2 True3 False4 Truedtype: bool0 False1 True2 False3 True4 Falsedtype: bool
复制代码


那既然有方法可以进行判断当前数据是否为缺失数据,那么我们用之前的方法与其配合,一样可以做滤除操作:


print(se[se.notnull()])
---0 4.02 8.04 5.0dtype: float64
复制代码


当然,除了 Series 对象之外,我们还需要进行处理 DataFrame 对象


df1=pd.DataFrame([[1,2,3],[NaN,NaN,2],[NaN,NaN,NaN],[8,8,NaN]]) print(df1)
--- 0 1 20 1.0 2.0 3.01 NaN NaN 2.02 NaN NaN NaN3 8.0 8.0 NaN
复制代码


默认滤除所有包含NaN


print(df1.dropna())
--- 0 1 20 1.0 2.0 3.0
复制代码


传⼊how=‘all’滤除全为NaN的⾏:


print(df1.dropna(how='all')) 
--- 0 1 20 1.0 2.0 3.01 NaN NaN 2.03 8.0 8.0 NaN
复制代码


可以看到,除了下标为2的那一行之外,其余含NaN值的行都被保留了。


之前操作最后只留下一行,原因是how的默认值为how='any'。只要是nan就删除


传⼊axis=1 滤除列:


print(df1.dropna(axis=1,how="all"))
--- 0 1 20 1.0 2.0 3.01 NaN NaN 2.02 NaN NaN NaN3 8.0 8.0 NaN
复制代码


为什么没有变化?按列来查看,没有一列是全是NaN值的。


除了how值外,我们还可以可以使用thresh来精准操作,它可以传入一个数值n,会保留至少n个非NaN数据的行或列:


print(df1.dropna(thresh=2))
--- 0 1 20 1.0 2.0 3.03 8.0 8.0 NaN
复制代码


仅有一个非NaN的行和全部为NaN的行就都被滤除了。


NaN是不是就只能被删除了呢?并不是,还记得我们之前操作的时候我提到过,我们大多数遇到NaN值的时候,基本都是用平均值来进行填充,这是一个惯例操作。


那么,我们来看看如何填充缺失数据


df1=pd.DataFrame([[1,2,3],[NaN,NaN,2],[NaN,NaN,NaN],[8,8,NaN]]) print(df1)
--- 0 1 20 1.0 2.0 3.01 NaN NaN 2.02 NaN NaN NaN3 8.0 8.0 NaN
复制代码


⽤常数填充fillna


print(df1.fillna(0))
--- 0 1 20 1.0 2.0 3.01 0.0 0.0 2.02 0.0 0.0 0.03 8.0 8.0 0.0
复制代码


传⼊inplace=True直接修改原对象:


df1.fillna(0,inplace=True) print(df1)
--- 0 1 20 1.0 2.0 3.01 0.0 0.0 2.02 0.0 0.0 0.03 8.0 8.0 0.0
复制代码


通过字典填充不同的常数


print(df1.fillna({0:10,1:20,2:30}))
--- 0 1 20 1.0 2.0 3.01 10.0 20.0 2.02 10.0 20.0 30.03 8.0 8.0 30.0
复制代码


还有我们之前提到过的,填充平均值:


print(df1.fillna(df1.mean()))
--- 0 1 20 1.0 2.0 3.01 4.5 5.0 2.02 4.5 5.0 2.53 8.0 8.0 2.5
复制代码


当然,我们可以只填充某一列


print(df1.iloc[:,1].fillna(5,inplace = True)) print(df1)
---None 0 1 20 1.0 2.0 3.01 NaN 5.0 2.02 NaN 5.0 NaN3 8.0 8.0 NaN
复制代码


传⼊method=” “会改变插值⽅式,先来一组数据,并在其中加上NaN


df2=pd.DataFrame(np.random.randint(0,10,(5,5))) df2.iloc[1:4,3]=NaNdf2.iloc[2:4,4]=NaNprint(df2)
--- 0 1 2 3 40 3 5 9 9.0 3.01 0 1 2 NaN 8.02 6 5 8 NaN NaN3 5 6 5 NaN NaN4 5 3 5 8.0 2.0
复制代码


现在,我们用前面的值来填充, method ='ffill'


print(df2.fillna(method='ffill'))
--- 0 1 2 3 40 3 5 9 9.0 3.01 0 1 2 9.0 8.02 6 5 8 9.0 8.03 5 6 5 9.0 8.04 5 3 5 8.0 2.0
复制代码


用后面的值来填充method='bfill':


print(df2.fillna(method='bfill',limit=1))
--- 0 1 2 3 40 7 1 8 0.0 0.01 6 8 4 NaN 5.02 6 2 5 NaN NaN3 4 8 0 1.0 3.04 8 0 2 1.0 3.0
复制代码


以上代码中,我们还传入了limit, 用于限制填充行数。


当我们传入axis的时候,会修改填充方向:


print(df2.fillna(method="ffill",limit=1,axis=1))
--- 0 1 2 3 40 0.0 8.0 9.0 4.0 7.01 2.0 8.0 0.0 0.0 9.02 1.0 8.0 5.0 5.0 NaN3 0.0 0.0 3.0 3.0 NaN4 2.0 9.0 4.0 6.0 3.0
复制代码


接着,我们再来看看如何移除重复数据,俗称「去重」:


DataFrame 中经常会出现重复⾏,利⽤duplicated()函数返回每⼀⾏判断是否重复的结果(重复则为 True)


df1=pd.DataFrame({'A':[1,1,1,2,2,3,1],'B':list("aabbbca")})print(df1)print(df1.duplicated())
--- A B0 1 a1 1 a2 1 b3 2 b4 2 b5 3 c6 1 a
0 False1 True2 False3 False4 True5 False6 Truedtype: bool
复制代码


去除全部的重复⾏


print(df1.drop_duplicates())
--- A B0 1 a2 1 b3 2 b5 3 c
复制代码


指定列去除重复行


print(df1.drop_duplicates(['A']))
--- A B0 1 a3 2 b5 3 c
复制代码


保留重复⾏中的最后⼀⾏


df1=pd.DataFrame({'A':[1,1,1,2,2,3,1],'B':list("aabbbca")})print(df1.drop_duplicates(['A'],keep='last'))
--- A B4 2 b5 3 c6 1 a
复制代码


去除重复的同时改变 DataFrame 对象


df1.drop_duplicates(['A','B'],inplace=True) print(df1)
--- A B0 1 a2 1 b3 2 b5 3 c
复制代码

数据合并

我们平时会与拿到的数据可能存储在不同的数据表中,这需要我们对数据进行合并,然后再进行操作。


使⽤join合并,着重关注的是⾏的合并


简单合并(默认是 left 左连接,以左侧df3为基础)


df3=pd.DataFrame({'Red':[1,3,5],'Green':[5,0,3]},index=list('abc'))df4=pd.DataFrame({'Blue':[1,9,8],'Yellow':[6,6,7]},index=list('cde')) print(df3)print(df4)df3.join(df4,how='left')
--- Red Greena 1 5b 3 0c 5 3 Blue Yellowc 1 6d 9 6e 8 7

Red Green Blue Yellowa 1 5 NaN NaNb 3 0 NaN NaNc 5 3 1.0 6.0
复制代码


右链接


df3.join(df4,how='outer')
--- Red Green Blue Yellowa 1.0 5.0 NaN NaNb 3.0 0.0 NaN NaNc 5.0 3.0 1.0 6.0d NaN NaN 9.0 6.0e NaN NaN 8.0 7.0
复制代码


合并多个 DataFrame 对象


df5=pd.DataFrame({'Brown':[3,4,5],'White':[1,1,2]},index=list('aed')) df3.join([df4,df5])
---
Red Green Blue Yellow Brown Whitea 1.0 5.0 NaN NaN 3.0 1.0b 3.0 0.0 NaN NaN NaN NaNc 5.0 3.0 1.0 6.0 NaN NaN
复制代码


使⽤merge,着重关注的是列的合并


我们先来构建两组数据:


df1=pd.DataFrame({'名字':list('ABCDE'),'性别':['男','⼥','男','男','⼥'],'职称': ['副教授','讲师','助教','教授','助教']},index=range(1001,1006))df1.columns.name='学院⽼师'df1.index.name='编号'df1
---学院⽼师 名字 性别 职称编号 1001 A 男 副教授1002 B ⼥ 讲师1003 C 男 助教1004 D 男 教授1005 E ⼥ 助教
复制代码


df2=pd.DataFrame({'名字':list('ABDAX'),'课程':['C++','计算机导论','汇编','数据结构','马克思原理'],'职称':['副教授','讲师','教授','副教授','讲师']},index= [1001,1002,1004,1001,3001])df2.columns.name='课程'df2.index.name='编号'print(df2)
---课程 名字 课程 职称编号 1001 A C++ 副教授1002 B 计算机导论 讲师1004 D 汇编 教授1001 A 数据结构 副教授3001 X 马克思原理 讲师
复制代码


默认下是根据左右对象中出现同名的列作为连接的键,且连接⽅式是how=’inner’


print(pd.merge(df1,df2)) # 返回匹配的
--- 名字 性别 职称 课程0 A 男 副教授 C++1 A 男 副教授 数据结构2 B ⼥ 讲师 计算机导论3 D 男 教授 汇编
复制代码


指定列名合并


pd.merge(df1,df2,on='名字',suffixes=['_1','_2']) # 返回匹配的
---
名字 性别 职称_1 课程 职称_20 A 男 副教授 C++ 副教授1 A 男 副教授 数据结构 副教授2 B ⼥ 讲师 计算机导论 讲师3 D 男 教授 汇编 教授
复制代码


连接⽅式,根据左侧为准


pd.merge(df1,df2,how='left')
--- 名字 性别 职称 课程0 A 男 副教授 C++1 A 男 副教授 数据结构2 B ⼥ 讲师 计算机导论3 C 男 助教 NaN4 D 男 教授 汇编5 E ⼥ 助教 NaN
复制代码


根据右侧为准


pd.merge(df1,df2,how='right')
--- 名字 性别 职称 课程0 A 男 副教授 C++1 B ⼥ 讲师 计算机导论2 D 男 教授 汇编3 A 男 副教授 数据结构4 X NaN 讲师 马克思原理
复制代码


所有的数据


pd.merge(df1,df2,how='outer')
---
名字 性别 职称 课程0 A 男 副教授 C++1 A 男 副教授 数据结构2 B ⼥ 讲师 计算机导论3 C 男 助教 NaN4 D 男 教授 汇编5 E ⼥ 助教 NaN6 X NaN 讲师 马克思原理
复制代码


根据多个键进⾏连接


pd.merge(df1,df2,*on*=['职称','名字'])
--- 名字 性别 职称 课程0 A 男 副教授 C++1 A 男 副教授 数据结构2 B ⼥ 讲师 计算机导论3 D 男 教授 汇编
复制代码


除此之外,我们还有一种轴向连接的方式:Concat


Series 对象的连接


s1=pd.Series([1,2],index=list('ab'))s2=pd.Series([3,4,5],index=list('bde')) print(s1)print(s2)pd.concat([s1,s2])
---a 1b 2dtype: int64b 3d 4e 5dtype: int64a 1b 2b 3d 4e 5dtype: int64
复制代码


横向连接


pd.concat([s1,s2],*axis*=1)
--- 0 1a 1.0 NaNb 2.0 3.0d NaN 4.0e NaN 5.0
复制代码


⽤内连接求交集(连接⽅式,共有’inner’,’left’,right’,’outer’)


pd.concat([s1,s2],axis=1,join='inner')
--- 0 1b 2 3
复制代码


创建层次化索引


pd.concat([s1,s2],keys=['A','B'])
---A a 1 b 2B b 3 d 4 e 5dtype: int64
复制代码


当纵向连接时 keys 为列名


pd.concat([s1,s2],keys=['A','D'],axis=1)
---- A Da 1.0 NaNb 2.0 3.0d NaN 4.0e NaN 5.0
复制代码


DataFrame 对象的连接


df3=pd.DataFrame({'Red':[1,3,5],'Green':[5,0,3]},index=list('abd')) df4=pd.DataFrame({'Blue':[1,9],'Yellow':[6,6]},index=list('ce'))pd.concat([df3,df4])
---
Red Green Blue Yellowa 1.0 5.0 NaN NaNb 3.0 0.0 NaN NaNd 5.0 3.0 NaN NaNc NaN NaN 1.0 6.0e NaN NaN 9.0 6.0
复制代码


⽤字典的⽅式连接同样可以创建层次化列索引


pd.concat({'A':df3,'B':df4},axis=1)
--- A B Red Green Blue Yellowa 1.0 5.0 NaN NaNb 3.0 0.0 NaN NaNd 5.0 3.0 NaN NaNc NaN NaN 1.0 6.0e NaN NaN 9.0 6.0
复制代码

多层索引(拓展)

创建多层索引


s = Series(np.random.randint(0,150,size=6),index=list('abcdef'))print(s)
---a 40b 122c 95d 40e 35f 27dtype: int64
复制代码


s = Series(np.random.randint(0,150,size=6),index=[['a','a','b','b','c','c'],['期中','期末','期中','期末','期中','期末']]) print(s)
---a 期中 132 期末 145b 期中 33 期末 149c 期中 10 期末 145dtype: int64
复制代码


DataFrame 也可以创建多层索引


# DataFrame创建多层索引df1 = df(np.random.randint(0,150,size=(6,4)),         columns = ['zs','ls','ww','zl'],         index =[['python','python','math','math','En','En'],['期中','期末','期中','期末','期中','期末']])print(df1)
--- zs ls ww zlpython 期中 123 3 98 95 期末 9 36 15 126math 期中 86 86 73 115 期末 3 130 52 89En 期中 75 21 84 98 期末 56 46 111 147
复制代码


特定结构


class1=['python','python','math','math','En','En']class2=['期中','期末','期中','期末','期中','期末']m_index2=pd.MultiIndex.from_arrays([class1,class2])df2=df(np.random.randint(0,150,(6,4)),index=m_index2) print(df2)
--- 0 1 2 3python 期中 94 36 6 19 期末 24 41 108 120math 期中 79 69 144 32 期末 138 100 42 38En 期中 110 90 123 75 期末 69 59 72 109
复制代码


class1=['期中','期中','期中','期末','期末','期末']class2=['python','math','En','python','math','En']m_index2=pd.MultiIndex.from_arrays([class1,class2])df2=df(np.random.randint(0,150,(6,4)),index=m_index2) print(df2)
--- 0 1 2 3期中 python 96 15 135 5 math 66 78 143 93 En 70 27 120 63期末 python 147 77 92 97 math 121 81 137 102 En 18 12 134 113
复制代码


product 构造


class1=['python','math','En']class2=['期中','期末']m_index2=pd.MultiIndex.from_product([class1,class2])df2=df(np.random.randint(0,150,(6,4)),index=m_index2) print(df2)
--- 0 1 2 3python 期中 12 72 115 59 期末 36 51 94 111math 期中 44 14 9 61 期末 115 121 65 93En 期中 29 23 16 70 期末 30 73 77 53
复制代码


多层索引对象的索引


让我们先来看看 Series 的操作


s = Series(np.random.randint(0,150,size=6),index=[['a','a','b','b','c','c'],['期中','期末','期中','期末','期中','期末']])print(s)
---a 期中 31 期末 4b 期中 101 期末 95c 期中 54 期末 126dtype: int64
复制代码


取⼀个第⼀级索引


print(s['a'])
---期中 31期末 4dtype: int64
复制代码


取多个第⼀级索引


print(s[['a','b']])
---a 期中 31 期末 4b 期中 101 期末 95dtype: int64
复制代码


根据索引获取值


print(s['a','期末'])
---4
复制代码


loc⽅法取值


print(s.loc['a'])print(s.loc[['a','b']]) print(s.loc['a','期末'])
---期中 31期末 4dtype: int64a 期中 31 期末 4b 期中 101 期末 95dtype: int644
复制代码


iloc⽅法取值(iloc 计算的事最内层索引)


print(s.iloc[1])print(s.iloc[1:4])
---4a 期末 4b 期中 101 期末 95dtype: int64
复制代码


然后再让我们来看看 DataFrame 的操作


# dataframeclass1=['python','math','En']class2=['期中','期末']m_index2=pd.MultiIndex.from_product([class1,class2])df2=df(np.random.randint(0,150,(6,4)),index=m_index2) print(df2)
--- 0 1 2 3python 期中 88 69 82 28 期末 110 60 130 133math 期中 64 103 24 49 期末 23 41 10 61En 期中 124 139 65 115 期末 114 13 117 79
复制代码


获取列


print(df2[0])
---python 期中 88 期末 110math 期中 64 期末 23En 期中 124 期末 114Name: 0, dtype: int64
复制代码


⼀级索引


print(df2.loc['python'])
--- 0 1 2 3期中 88 69 82 28期末 110 60 130 133
复制代码


多个⼀级索引


print(df2.loc[['python','math']])
--- 0 1 2 3python 期中 88 69 82 28 期末 110 60 130 133math 期中 64 103 24 49 期末 23 41 10 61
复制代码


取⼀⾏


print(df2.loc['python','期末'])
---0 1101 602 1303 133Name: (python, 期末), dtype: int64
复制代码


取⼀值


print(df2.loc['python','期末'][0])
---110
复制代码


iloc 是只取最内层的索引的


print(df2.iloc[0])
---0 881 692 823 28Name: (python, 期中), dtype: int64
复制代码

时间序列

⽣成⼀段时间范围


该函数主要⽤于⽣成⼀个固定频率的时间索引,在调⽤构造⽅法时,必须指定startendperiods中的两个参数值,否则报错。



date = pd.date_range(start='20190501',end='20190530')print(date)
---DatetimeIndex(['2023-05-01', '2023-05-02', '2023-05-03', '2023-05-04', '2023-05-05', '2023-05-06', '2023-05-07', '2023-05-08', '2023-05-09', '2023-05-10', '2023-05-11', '2023-05-12', '2023-05-13', '2023-05-14', '2023-05-15', '2023-05-16', '2023-05-17', '2023-05-18', '2023-05-19', '2023-05-20', '2023-05-21', '2023-05-22', '2023-05-23', '2023-05-24', '2023-05-25', '2023-05-26', '2023-05-27', '2023-05-28', '2023-05-29', '2023-05-30'], dtype='datetime64[ns]', freq='D')
复制代码


req:⽇期偏移量,取值为 string, 默认为'D',


periods:固定时期,取值为整数或 None


freq: 时间序列频率


date = pd.date_range(start='20230501',periods=10,freq='10D')print(date)
---DatetimeIndex(['2023-05-01', '2023-05-11', '2023-05-21', '2023-05-31', '2023-06-10', '2023-06-20', '2023-06-30', '2023-07-10', '2023-07-20', '2023-07-30'], dtype='datetime64[ns]', freq='10D')
复制代码


根据closed参数选择是否包含开始和结束时间closed=None,left 包含开始时间,不包含结束时间, right 与之相反。


data_time =pd.date_range(start='2023-08-09',end='2023-08-14',closed='left') print(data_time)
---DatetimeIndex(['2023-08-09', '2023-08-10', '2023-08-11', '2023-08-12', '2023-08-13'], dtype='datetime64[ns]', freq='D')
复制代码


时间序列在 dataFrame 中的作⽤


可以将时间作为索引


index = pd.date_range(start='20230801',periods=10)df = pd.Series(np.random.randint(0,10,size = 10),index=index) print(df)
---2023-08-01 72023-08-02 22023-08-03 52023-08-04 52023-08-05 22023-08-06 02023-08-07 22023-08-08 32023-08-09 62023-08-10 5Freq: D, dtype: int64
复制代码


truncate这个函数将 before 指定⽇期之前的值全部过滤出去,after 指定⽇期之前的值全部过滤出去.


after = df.truncate(after='2023-08-8')print(after)
---2023-08-01 72023-08-02 22023-08-03 52023-08-04 52023-08-05 22023-08-06 02023-08-07 22023-08-08 3Freq: D, dtype: int64
复制代码


long_ts = pd.Series(np.random.randn(1000),index=pd.date_range('1/1/2021',periods=1000)) print(long_ts)
---2021-01-01 -0.482811...2023-09-27 -0.108047Freq: D, Length: 1000, dtype: float64
复制代码


根据年份获取


result = long_ts['2022']print(result)
---2022-01-01 -0.600007...2022-12-31 0.097874Freq: D, Length: 365, dtype: float64
复制代码


年份和⽇期获取


result = long_ts['2023-07'] print(result)
---2023-07-01 -1.797582...2023-07-31 0.687787Freq: D, dtype: float64
复制代码


使⽤切⽚


# 使⽤切⽚result = long_ts['2023-05-01':'2023-05-06']print(result)
---2023-05-01 -2.3382182023-05-02 -2.1307802023-05-03 0.5829202023-05-04 -0.1825402023-05-05 0.1273632023-05-06 -0.032844Freq: D, dtype: float64
复制代码


通过between_time()返回位于指定时间段的数据集


index=pd.date_range("2023-03-17","2023-03-30",freq="2H")ts = pd.Series(np.random.randn(157),index=index) print(ts.between_time("7:00","17:00"))
---2023-03-17 08:00:00 -0.532254...2023-03-29 16:00:00 0.437697Length: 65, dtype: float64
复制代码


这些操作也都适⽤于 dataframe


index=pd.date_range('1/1/2023',periods=100)df = pd.DataFrame(np.random.randn(100,4),index=index) print(df.loc['2023-04'])
--- 0 1 2 32023-04-01 -0.220090 0.335770 -0.086181 -0.0460452023-04-02 -1.046423 -0.347116 0.367099 -0.9793542023-04-03 -0.720944 -1.478932 0.220948 0.8018312023-04-04 1.359946 -1.239004 0.309747 -0.0479592023-04-05 -0.256502 2.224782 0.494740 -1.3224902023-04-06 1.488119 0.244942 0.614101 -0.1562012023-04-07 -1.815019 -1.935966 0.239024 -1.3885022023-04-08 1.106623 1.148805 2.120405 -0.7992902023-04-09 -1.902216 0.625965 -0.102506 -0.4305502023-04-10 -0.876382 -2.034205 -0.060846 2.442651
复制代码


移位⽇期


ts = pd.Series(np.random.randn(10),index=pd.date_range('1/1/2023',periods=10)) print(ts)
---2023-01-01 -0.9769582023-01-02 -0.4874392023-01-03 0.1431042023-01-04 -0.9642362023-01-05 0.7583262023-01-06 -1.6508182023-01-07 0.7092312023-01-08 0.1987142023-01-09 -1.0434432023-01-10 0.220834Freq: D, dtype: float64
复制代码


移动数据,索引不变,默认由NaN填充


periods: 移动的位数 负数是向上移动


fill_value: 移动后填充数据


freq: ⽇期偏移量


ts.shift(periods=2,fill_value=100, freq='D')
---2023-01-03 -0.9769582023-01-04 -0.4874392023-01-05 0.1431042023-01-06 -0.9642362023-01-07 0.7583262023-01-08 -1.6508182023-01-09 0.7092312023-01-10 0.1987142023-01-11 -1.0434432023-01-12 0.220834Freq: D, dtype: float64
复制代码


通过tshift()将索引移动指定的时间:


ts.tshift(2)
---2023-01-03 -0.9769582023-01-04 -0.4874392023-01-05 0.1431042023-01-06 -0.9642362023-01-07 0.7583262023-01-08 -1.6508182023-01-09 0.7092312023-01-10 0.1987142023-01-11 -1.0434432023-01-12 0.220834Freq: D, dtype: float64
复制代码


将时间戳转化成时间根式


pd.to_datetime(1688570740000,unit='ms')
---Timestamp('2023-07-05 15:25:40')
复制代码


utc 是协调世界时,时区是以 UTC 的偏移量的形式表示的,但是注意设置utc=True,是让 pandas 对象具有时区性质,对于⼀列进⾏转换的,会造成转换错误。


unit='ms'设置粒度是到毫秒级别的。


时区名字


import pytzprint(pytz.common_timezones)
---['Africa/Abidjan', ..., 'US/Pacific', 'UTC']
复制代码


pd.to_datetime(1688570740000,unit='ms').tz_localize('UTC').tz_convert('Asia/Shanghai')
---Timestamp('2023-07-05 23:25:40+0800', tz='Asia/Shanghai')
复制代码


一个处理的例子:


df = pd.DataFrame([1688570740000, 1688570800000, 1688570860000],columns = ['time_stamp'])pd.to_datetime(df['time_stamp'],unit='ms').dt.tz_localize('UTC').dt.tz_convert ('Asia/Shanghai')
---0 2023-07-05 23:25:40+08:001 2023-07-05 23:26:40+08:002 2023-07-05 23:27:40+08:00Name: time_stamp, dtype: datetime64[ns, Asia/Shanghai]
复制代码


先赋予标准时区,再转换到东⼋区。


处理中⽂


pd.to_datetime('2023年7⽉5⽇',format='%Y年%m⽉%d⽇')
---Timestamp('2023-07-05 00:00:00')
复制代码

分组聚合

df=pd.DataFrame({'name':['BOSS','Lilei','Lilei','Han','BOSS','BOSS','Han','BOSS'], 'Year':[2016,2016,2016,2016,2017,2017,2017,2017],'Salary':[999999,20000,25000,3000,9999999,999999,3500,999999],'Bonus':[100000,20000,20000,5000,200000,300000,3000,400000]})df
---
name Year Salary Bonus0 BOSS 2016 999999 1000001 Lilei 2016 20000 200002 Lilei 2016 25000 200003 Han 2016 3000 50004 BOSS 2017 9999999 2000005 BOSS 2017 999999 3000006 Han 2017 3500 30007 BOSS 2017 999999 400000
复制代码


根据 name 这⼀列进⾏分组


group_by_name=df.groupby('name') print(type(group_by_name))
---<class 'pandas.core.groupby.generic.DataFrameGroupBy'>
复制代码


查看分组


print(group_by_name.groups) # 分组后的数量print(group_by_name.count())
---{'BOSS': [0, 4, 5, 7], 'Han': [3, 6], 'Lilei': [1, 2]} Year Salary Bonusname BOSS 4 4 4Han 2 2 2Lilei 2 2 2
复制代码


查看分组的情况


for name,group in group_by_name:     print(name)    ---BOSSHanLilei
复制代码


组的名字


print(group) # 组具体内容
--- name Year Salary Bonus1 Lilei 2016 20000 200002 Lilei 2016 25000 20000
复制代码


可以选择分组


print(group_by_name.get_group('BOSS'))
--- name Year Salary Bonus0 BOSS 2016 999999 1000004 BOSS 2017 9999999 2000005 BOSS 2017 999999 3000007 BOSS 2017 999999 400000
复制代码


按照某⼀列进⾏分组, 将 name 这⼀列作为分组的键,对 year 进⾏分组


group_by_name=df['Year'].groupby(df['name'])print(group_by_name.count())
---nameBOSS 4Han 2Lilei 2Name: Year, dtype: int64
复制代码


按照多列进⾏分组


group_by_name_year=df.groupby(['name','Year'])for name,group in group_by_name_year:     print(name) # 组的名字    print(group) # 组具体内容    ---('BOSS', 2016)   name  Year  Salary   Bonus0  BOSS  2016  999999  100000('BOSS', 2017)   name  Year   Salary   Bonus4  BOSS  2017  9999999  2000005  BOSS  2017   999999  3000007  BOSS  2017   999999  400000('Han', 2016)  name  Year  Salary  Bonus3  Han  2016    3000   5000('Han', 2017)  name  Year  Salary  Bonus6  Han  2017    3500   3000('Lilei', 2016)    name  Year  Salary  Bonus1  Lilei  2016   20000  200002  Lilei  2016   25000  20000
复制代码


可以选择分组


print(group_by_name_year.get_group(('BOSS',2016)))
--- name Year Salary Bonus0 BOSS 2016 999999 100000
复制代码


将某列数据按数据值分成不同范围段进⾏分组(groupby)运算


df = pd.DataFrame({'Age': np.random.randint(20, 70, 100), 'Sex': np.random.choice(['M', 'F'], 100), })age_groups = pd.cut(df['Age'], bins=[19,40,65,100])print(df.groupby(age_groups).count())
--- Age SexAge (19, 40] 35 35(40, 65] 54 54(65, 100] 11 11
复制代码


按‘Age’分组范围和性别(sex)进⾏制作交叉表


pd.crosstab(age_groups, df['Sex'])
---
Sex F MAge (19, 40] 18 22(40, 65] 25 27(65, 100] 3 5
复制代码

聚合

我们先来看聚合函数的表格



df1=pd.DataFrame({'Data1':np.random.randint(0,10,5), 'Data2':np.random.randint(10,20,5), 'key1':list('aabba'), 'key2':list('xyyxy')})print(df1)
--- Data1 Data2 key1 key20 4 17 a x1 4 13 a y2 0 12 b y3 5 16 b x4 8 10 a y
复制代码


按 key1 分组,进⾏聚合计算


⚠️ 注意:当分组后进⾏数值计算时,不是数值类的列(即麻烦列)会被清除


print(df1.groupby('key1').sum())
--- Data1 Data2key1 a 16 40b 5 28
复制代码


只算 data1


print(df1['Data1'].groupby(df1['key1']).sum()) print(df1.groupby('key1')['Data1'].sum())
---key1a 16b 5Name: Data1, dtype: int64key1a 16b 5Name: Data1, dtype: int64
复制代码


使⽤agg()函数做聚合运算


print(df1.groupby('key1').agg('sum'))
--- Data1 Data2key1 a 16 40b 5 28
复制代码


可以同时做多个聚合运算


print(df1.groupby('key1').agg(['sum','mean','std']))
--- Data1 Data2 sum mean std sum mean stdkey1 a 16 5.333333 2.309401 40 13.333333 3.511885b 5 2.500000 3.535534 28 14.000000 2.828427
复制代码


可⾃定义函数,传⼊agg⽅法中 grouped.agg(func)


def peak_range(df):    """    返回数值范围    """    return df.max() - df.min()print(df1.groupby('key1').agg(peak_range))
--- Data1 Data2key1 a 4 7b 5 4
复制代码


同时应⽤多个聚合函数


print(df1.groupby('key1').agg(['mean', 'std', 'count', peak_range])) # 默认列名为函数名
--- Data1 Data2 \ mean std count peak_range mean std count key1 a 5.333333 2.309401 3 4 13.333333 3.511885 3 b 2.500000 3.535534 2 5 14.000000 2.828427 2
peak_range key1 a 7 b 4
复制代码


通过元组提供新的列名


print(df1.groupby('key1').agg(['mean', 'std', 'count', ('range', peak_range)])) 
--- Data1 Data2 mean std count range mean std count rangekey1 a 5.333333 2.309401 3 4 13.333333 3.511885 3 7b 2.500000 3.535534 2 5 14.000000 2.828427 2 4
复制代码


给每列作⽤不同的聚合函数


dict_mapping = {    'Data1':['mean','max'],    'Data2':'sum'}df1.groupby('key1').agg(dict_mapping)
--- Data1 Data2 mean max sumkey1 a 5.333333 8 40b 2.500000 5 28
复制代码


拓展apply()函数


apply 函数是 pandas⾥⾯所有函数中⾃由度最⾼的函数


df1=pd.DataFrame({'sex':list('FFMFMMF'),'smoker':list('YNYYNYY'),'age': [21,30,17,37,40,18,26],'weight':[120,100,132,140,94,89,123]})print(df1)
--- sex smoker age weight0 F Y 21 1201 F N 30 1002 M Y 17 1323 F Y 37 1404 M N 40 945 M Y 18 896 F Y 26 123
复制代码


抽烟的年龄⼤于等 18 的


def bin_age(age):    if age >=18:        return 1     else:        return 0      print(df1['age'].apply(bin_age))
---0 11 12 03 14 15 16 1Name: age, dtype: int64
复制代码


df1['age'] = df1['age'].apply(bin_age) print(df1)
--- sex smoker age weight0 F Y 1 1201 F N 1 1002 M Y 0 1323 F Y 1 1404 M N 1 945 M Y 1 896 F Y 1 123
复制代码


取出抽烟和不抽烟的体重前⼆


def top(smoker,col,n=5):    return smoker.sort_values(by=col)[-n:]df1.groupby('smoker').apply(top,col='weight',n=2)
--- sex smoker age weightsmoker N 4 M N 1 94 1 F N 1 100 Y 2 M Y 0 132 3 F Y 1 140
复制代码


按理来说,我们最后展示数据的时候,在用完age0,1作为判断之后,要恢复成原本的年龄的数据。不过...就这样吧。因为马上,我们要做一个完整的分组案例,从一个csv文件获取数据,然后分组,最后进行数据可视化展示:

分组案例

我们先来读取数据,案例中使用到的数据会上传到我的Github仓库中。


data = pd.read_csv('./data/movie_metadata.csv')print('数据的形状:', data.shape) print(data.head())
---数据的形状: (5043, 28) movie_title language country \0 Avatar English USA 1 Pirates of the Caribbean: At World's End English USA 2 Spectre English UK 3 The Dark Knight Rises English USA 4 Star Wars: Episode VII - The Force Awakens ... NaN NaN
content_rating title_year color duration genres \0 PG-13 2009-02 Color 178.0 Action|Adventure|Fantasy|Sci-Fi 1 PG-13 2007-09 Color 169.0 Action|Adventure|Fantasy 2 PG-13 2015-11 Color 148.0 Action|Adventure|Thriller 3 PG-13 2012-08 Color 164.0 Action|Thriller 4 NaN NaN NaN NaN Documentary
plot_keywords budget ... \0 avatar|future|marine|native|paraplegic 237000000.0 ... 1 goddess|marriage ceremony|marriage proposal|pi... 300000000.0 ... 2 bomb|espionage|sequel|spy|terrorist 245000000.0 ... 3 deception|imprisonment|lawlessness|police offi... 250000000.0 ... 4 NaN NaN ...
actor_2_facebook_likes actor_3_name actor_3_facebook_likes \0 936.0 Wes Studi 855.0 1 5000.0 Jack Davenport 1000.0 ...
复制代码


然后让我们来处理缺失值:


data = data.dropna(how='any')print(data.head())
--- movie_title language country content_rating \0 Avatar English USA PG-13 1 Pirates of the Caribbean: At World's End English USA PG-13 2 Spectre English UK PG-13 3 The Dark Knight Rises English USA PG-13 5 John Carter English USA PG-13
title_year color duration genres \0 2009-02 Color 178.0 Action|Adventure|Fantasy|Sci-Fi 1 2007-09 Color 169.0 Action|Adventure|Fantasy 2 2015-11 Color 148.0 Action|Adventure|Thriller 3 2012-08 Color 164.0 Action|Thriller 5 2012-07 Color 132.0 Action|Adventure|Sci-Fi
plot_keywords budget ... \0 avatar|future|marine|native|paraplegic 237000000.0 ... 1 goddess|marriage ceremony|marriage proposal|pi... 300000000.0 ... 2 bomb|espionage|sequel|spy|terrorist 245000000.0 ... 3 deception|imprisonment|lawlessness|police offi... 250000000.0 ... 5 alien|american civil war|male nipple|mars|prin... 263700000.0 ...
actor_2_facebook_likes actor_3_name actor_3_facebook_likes \0 936.0 Wes Studi 855.0 1 5000.0 Jack Davenport 1000.0 2 393.0 Stephanie Sigman 161.0 ...
复制代码


接着,我们来查看一下票房收入统计


导演 vs 票房总收⼊


group_director = data.groupby(*by*='director_name')['gross'].sum()
复制代码


ascending 升降序排列,True 升序


result = group_director.sort_values() print(type(result))print(result)
---<class 'pandas.core.series.Series'>director_nameEkachai Uekrongtham 1.620000e+02Frank Whaley 7.030000e+02Ricki Stern 1.111000e+03Alex Craig Mann 1.332000e+03Paul Bunnell 2.436000e+03 ... Sam Raimi 2.049549e+09Tim Burton 2.071275e+09Michael Bay 2.231243e+09Peter Jackson 2.592969e+09Steven Spielberg 4.114233e+09Name: gross, Length: 1660, dtype: float64
复制代码


电影产量年份趋势


from matplotlib import pyplot as plt import randomfrom matplotlib import font_manager
movie_years = data.groupby('title_year')['movie_title']print(movie_years.count().index.tolist()) print(movie_years.count().values)
---['1927-02', ..., '2016-12'][ 1 ... 6]
复制代码


最后,我们利用之前学过的matplotlib进行数据可视化展示:


x = movie_years.count().index.tolist()y = movie_years.count().valuesplt.figure(figsize=(20,8),dpi=80)plt.plot(x,y)plt.show()
复制代码



结尾

那小伙伴们,到今天为止,我们《AI 秘籍》系列课程中的「Python 部分」也就完全讲完了。就如我一开始向大家承诺的,这部分内容将完全免费。


而我们的《AI 秘籍》课程也才刚刚开始,未来很长一段时间内,我们都将继续和这些基础内容频繁打交道。用它们来呈现我们之后要用到的所有课程。包括 AI 基础,CV,BI,NLP 等课程。


不过在结束了一个阶段之后,我需要花点时间休整一下,也是为了好好的备课,找到最好的结构和顺序为大家编写后面的课程。本次课程的最后这两节课我都有些疲劳,为了赶快完成进度,编写过程当中可能有些粗糙或者遗漏,也请大家多包涵。日后,我可能会对这两节课进行更新,将一些细节的讲解补充完整。


免费部分结束了,日后的收费课程,也期望大家能一如即往的支持。


在这里,我也给大家推荐一些比较好的书籍,希望看到小伙伴们能够快速成长起来。


感谢大家,好了。本节课到此结束,下课了。

发布于: 1 小时前阅读数: 2
用户头像

茶桁

关注

还未添加个人签名 2020-10-20 加入

还未添加个人简介

评论

发布
暂无评论
27. Pandas_Python_茶桁_InfoQ写作社区