Pandas基础操作(上)

文章目录

  • 一、Pandas文件读取
    • 1.pandas数据读取
    • 1、读取纯文本文件
      • 1.1 读取csv,使用默认的标题行、逗号分隔符
      • 1.2 读取txt文件,自己指定分隔符、列名
    • 2、读取excel文件
    • 3、读取sql文件
  • 二、pandas的数据结构DataFrame和Series
      • DataFrame:二维数据,整个表格,多行多列
    • 1.Series
      • 1.1 仅有数据列表即可生产最简单的Series
      • 1.2 创建一个具有标签索引的Series
      • 1.3 使用python字典创建Series
      • 1.4 根据数据标签索引查询数据
    • 2. DataFrame
      • 2.1 根据多个字典序列创建DataFrame
    • 从DataFrame中查询出Series
      • 3.1 查询一列,结果是一个pd.Series
      • 3.2 查询多列,结果是一个pd.DataFrame
      • 3.3 查询一行,结果是一个pd.Series
      • 3.4 查询多行,结果是一个pd.DataFrame
  • 三.Pandas查询数据的5种方法
    • Pandas查询数据的几种方法
    • Pandas使用df.loc查询数据的方法
    • 注意
    • 0. 读取数据
    • 1. 使用单个label值查询数据
    • 2. 使用值列进行表批量查询
    • 3. 使用数值区间进行范围查询
    • 4. 使用条件表达式查询
      • 复杂条件查询,查询一下完美得天气
    • 5. 调用函数查询
  • 四、Pandas怎样新增数据列
    • 0. 读取csv数据到DataFrame
    • 1. 直接赋值的方法
    • 2. df.apply方法
    • 3. df.assign方法
    • 4. 按条件选择分组分别赋值
  • 五、Pandas数据统计函数
    • 0. 读取csv数据
    • 1. 汇总类统计
    • 2. 唯一去重和按值计数
      • 2.1 唯一去重
      • 2.2 按值计数
    • 3. 相关系数和协防差
  • 六、Pandas对缺失值的处理
    • 实例:特殊excel的读取、清洗、处理
    • 步骤1:读取excel的时候,忽略前几个空行
    • 步骤2:检测空值
    • 步骤3:删除掉全是空值的列
    • 步骤4:删除掉全是空值的行
    • 步骤5:将分数列为空的填充为0分
    • 步骤6:将姓名的缺失值填充
    • 步骤7:将清晰好的excel保存
  • 七、Pandas的SettingWithCopyWarning报警
    • 0. 数据读取
    • 1. 复现
    • 2、原因
    • 4. 解决方法2
      • Pandas不允许先筛选子DataFrame,在进行修改写入
  • 八、Pandas数据排序
    • 0. 读取数据
    • 1. Series的排序
    • 2. DataFrame的排序
      • 2.1 单列排序
      • 2.2 多列排序
  • 九、Pandas字符串处理
    • 0. 读取北京2018年天气数据
    • 1. 获取Series的str属性,使用各种字符串处理函数
    • 4. 使用正则表达式的处理
      • Series.str默认就开启了正则表达式模式
  • 十、Pandas的axis参数怎么理解?
      • ***按哪个axis,就是这个axis要动起来(类似被for遍历),其它的axis保持不动\***
    • 1. 单列drop, 就是删除某一列
    • 3. 按照axis=0/index执行mean聚合操作
      • ***按哪个axis,就是这个axis要动起来(类似被for遍历),其它的axis保持不动\***
    • 3. 按照axis=1/columns执行mean聚合操作
      • ***按哪个axis,就是这个axis要动起来(类似被for遍历),其它的axis保持不动\***
    • 5. 再次举例, 加深理解
      • ***按哪个axis,就是这个axis要动起来(类似被for遍历),其它的axis保持不动\***
  • 十一、Pandas的索引index的用途
    • 1. 使用index查询数据
    • 2. 使用index会提升查询性能
    • 实验1:完全随机的查询顺序
    • 实验2:将index排序后的查询
    • 3.使用index能自动对齐数据
      • s1,s2都具有b,c索引,而a,d为各自独有,无法对齐,所有相加结果为空
    • 4. 使用index更多更强大的数据结构支持
  • 十二、Pandas怎样实现DataFrame的Merge
      • merge的语法:
    • 1、电影数据集的join实例
        • 电影评分数据集
    • 2、理解merge时数量的对齐关系
      • 2.1 one-to-one 一对一关系的merge
      • 2.2 one-to-many 一对多关系的merge
      • 2.3 many-to-many 多对多关系的merge
    • 3、理解left join、right join、inner join、outer join的区别
      • 3.1 inner join,默认
      • 3.2 left join
      • 3.3 right join
      • 3.4 outer join
    • 4、如果出现非Key的字段重名怎么办
  • 十三、Pandas实现数据的合并concat
        • 使用场景:
        • 一句话说明concat语法:
        • concat语法:pandas.concat(objs, axis=0, join='outer', ignore_index=False)
        • append语法:DataFrame.append(other, ignore_index=False)
        • 参考文档:
    • 一、使用Pandas.concat合并数据
    • 1. 默认的concat, 参数为axis=0, join=outer, ignore_index=False
    • 2. 使用ignore_index=True可以忽略原来的索引
    • 3. 使用join=inner过滤掉不匹配的列
    • 4. 使用axis=1相当于添加新列
      • A:添加一列Series
      • B:添加多列Series
    • 二、使用DateFrame.append按行合并数据
      • 1. 给一个DataFrame添加另一个DataFrame
      • 2. 忽略原来的索引,另ignore_index=True
      • 3.可以一行一行的给DataFrame添加数据
      • A:低性能版
      • B:性能好的版本
  • 十四、Pandas批量拆分Excel与合并Excel
    • 0. 读取源Excel到Pandas
    • 1、将一个大excel等份拆成多个Excel
      • 1. 1 计算拆分后的每个excel的行数
      • 1.2 拆分成多个DataFrame
      • 1.3 将每个DataFrame存入excel
    • 2、合并多个小Excel到一个大Excel
      • 2.1 遍历文件夹,得到要合并的Excel名称列表
      • 2.2 分别读取到DataFrame
      • 2.3 使用pd.concat进行合并
      • 2.4 将合并后的DataFrame输出到Excel
  • 十五、Pandas怎样实现groupby分组统计
    • 1、分组使用聚合函数做数据统计
      • 1.1 单个列groupby,查询所有数据列的统计
      • 1.2 多个列groupby,查询所有数据列的统计
      • 1.3 同时查看多种数据统计
      • 1.4 查看单列的结果数据统计
      • 1.5 不同列使用不同的聚合函数
    • 2、遍历groupby的结果理解执行流程
      • 2.1 遍历单个列聚合的分组
      • 2.2 遍历多个列聚合的分组
    • 3、实例分组探索天气数据
      • 3.1 查看每个月的最高温度
      • 3.2 查看每个月的最高温度、最低温度、平均空气质量指数
  • 十六、Pandas的分层索引MultiIndex
    • 1、Series的分层索引MultiIndex
    • 2、Series有多层索引MultiIndex怎么筛选数据?
    • 3、DataFrame的多层索引MultiIndex
    • 4、DataFrame有多层索引MultiIndex怎样筛选?
  • 十七、Pandas的数据转换函数map、apply、applymap
    • 1. map用于Series值的转换
      • 方法1:Series.map(dict)
      • 方法2:Series.map(function)
    • 2. apply用于Series和DataFrame的转换
      • Series.apply(function)
      • DataFrame.apply(function)
    • 3. applymap用于DataFrame所有值的转换
  • 十八、Pandas怎样对每个分组应用apply函数?
      • 知识:Pandas的GroupBy遵从split、apply、combine模式
      • GroupBy.apply(function)
      • 本次实例演示:
    • 实例1:怎样对数值列按分组的归一化?
      • 演示:用户对电影评分的归一化
    • 实例2:怎么取每个分组的TOP N数据

一、Pandas文件读取

1.pandas数据读取

pandas需要先读取表格类型的数据,然后进行分析

数据类型 说明 pandas读取方法
csv、tsv、txt 用逗号分隔、tab分割的纯文本文件 pd.read_csv
excel 微软xls或者xlsx文件 pd.read_excel
mysql 关系型数据库表 pd.read_sql

In [1]:

import pandas as pd

1、读取纯文本文件

1.1 读取csv,使用默认的标题行、逗号分隔符

In [2]:

fpath = "./pandas-learn-code/datas/ml-latest-small/ratings.csv"

In [3]:

# 使用pd.read_csv读取数据
ratings = pd.read_csv(fpath)

In [4]:

# 查看前几行数据
ratings.head()

Out[4]:

userId movieId rating timestamp
0 1 1 4.0 964982703
1 1 3 4.0 964981247
2 1 6 4.0 964982224
3 1 47 5.0 964983815
4 1 50 5.0 964982931

In [5]:

# 查看数据的形状,返回(行数、列数)
ratings.shape

Out[5]:

(100836, 4)

In [6]:

# 查看列名列表
ratings.columns

Out[6]:

Index(['userId', 'movieId', 'rating', 'timestamp'], dtype='object')

In [7]:

# 查看索引
ratings.index

Out[7]:

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

In [9]:

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

Out[9]:

userId         int64
movieId        int64
rating       float64
timestamp      int64
dtype: object

1.2 读取txt文件,自己指定分隔符、列名

In [10]:

fpath = "./pandas-learn-code/datas/crazyant/access_pvuv.txt"

In [11]:

pvuv = pd.read_csv(fpath, sep="\t", header=None, names=["pdate","pv","uv"])
  • sep代表分隔符
  • header=none代表没有列名
  • names代表指定的列明

In [13]:

pvuv.head()

Out[13]:

pdate pv uv
0 2019-09-10 139 92
1 2019-09-09 185 153
2 2019-09-08 123 59
3 2019-09-07 65 40
4 2019-09-06 157 98

2、读取excel文件

In [18]:

fpath = "./pandas-learn-code/datas/crazyant/access_pvuv.xlsx"
pvuv = pd.read_excel(fpath)

In [19]:

pvuv

Out[19]:

日期 PV UV
0 2019-09-10 139 92
1 2019-09-09 185 153
2 2019-09-08 123 59
3 2019-09-07 65 40
4 2019-09-06 157 98
5 2019-09-05 205 151
6 2019-09-04 196 167
7 2019-09-03 216 176
8 2019-09-02 227 148
9 2019-09-01 105 61

3、读取sql文件

In [36]:

import pymysql
conn = pymysql.connect(
    host="127.0.0.1",
    user="root",
    password="123456",
    database="test",
    charset="utf8"
)

In [41]:

fpath = "./pandas-learn-code/datas/crazyant/test_crazyant_pvuv.sql"
mysql_page = pd.read_sql("select * from crazyant_pvuv", con=conn)

In [42]:

pvuv

Out[42]:

日期 PV UV
0 2019-09-10 139 92
1 2019-09-09 185 153
2 2019-09-08 123 59
3 2019-09-07 65 40
4 2019-09-06 157 98
5 2019-09-05 205 151
6 2019-09-04 196 167
7 2019-09-03 216 176
8 2019-09-02 227 148
9 2019-09-01 105 61

二、pandas的数据结构DataFrame和Series

DataFrame:二维数据,整个表格,多行多列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tT4RRssV-1597761927694)(C:\Users\z&y\AppData\Roaming\Typora\typora-user-images\image-20200730213558995.png)]

In [1]:

import pandas as pd
import numpy as np

1.Series

Series是一种类似于一维数组的对象,它由一组数据(不同数据类型)以及一组与之相关的数据标签(即索引)组成。

1.1 仅有数据列表即可生产最简单的Series

In [3]:

s1 = pd.Series([1,'a',5.2,7])

In [5]:

# 左侧为索引,右侧是数据
s1.head()

Out[5]:

0      1
1      a
2    5.2
3      7
dtype: object

In [6]:

# 获取索引
s1.index

Out[6]:

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

In [7]:

# 获取数据
s1.values

Out[7]:

array([1, 'a', 5.2, 7], dtype=object)

1.2 创建一个具有标签索引的Series

In [8]:

s2 = pd.Series([1,'a',5.2,7], index=['a','b','c','d'])

In [9]:

s2

Out[9]:

a      1
b      a
c    5.2
d      7
dtype: object

In [10]:

s2.index

Out[10]:

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

1.3 使用python字典创建Series

In [11]:

sdata = {'Ohio':35000, 'Texas':72000, 'Oregon':16000, 'Utah':5000}

In [13]:

s3 = pd.Series(sdata)

In [14]:

# 字典的key成为了Series的索引
s3

Out[14]:

Ohio      35000
Texas     72000
Oregon    16000
Utah       5000
dtype: int64

1.4 根据数据标签索引查询数据

类似python的字典dict

In [15]:

s2

Out[15]:

a      1
b      a
c    5.2
d      7
dtype: object

In [20]:

s2['a']

Out[20]:

1

In [21]:

# 查询一个值,返回查询值的数据类型
type(s2['a'])

Out[21]:

int

In [18]:

# 一次查询多个值
s2[['a','b','c']]

Out[18]:

a      1
b      a
c    5.2
dtype: object

In [22]:

# 查询多个值,返回的还是Series
type(s2[['a','b','c']])

Out[22]:

pandas.core.series.Series

2. DataFrame

DataFrame是一个表格型的数据结构

  • 每列可以是不同的值类型(数值,字符串,布尔值等)
  • 既有行索引index,也有列索引columns
  • 可以被看做由Series组成的字典

2.1 根据多个字典序列创建DataFrame

In [24]:

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

In [25]:

df

Out[25]:

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

In [26]:

df.dtypes

Out[26]:

state     object
year       int64
pop      float64
dtype: object

In [27]:

df.columns

Out[27]:

Index(['state', 'year', 'pop'], dtype='object')

In [28]:

df.index

Out[28]:

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

从DataFrame中查询出Series

  • 如果只查询一列,一列,返回的是pd.Series
  • 如果查询多行,多列,返回的是pd.DataFrame

In [29]:

df

Out[29]:

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

3.1 查询一列,结果是一个pd.Series

In [30]:

df['year']

Out[30]:

0    2000
1    2001
2    2002
3    2003
4    2004
Name: year, dtype: int64

In [35]:

# 返回的是一个Series
type(df['year'])

Out[35]:

pandas.core.series.Series

3.2 查询多列,结果是一个pd.DataFrame

In [33]:

df[['year', 'pop']]

Out[33]:

year pop
0 2000 1.5
1 2001 1.7
2 2002 3.6
3 2003 2.4
4 2004 2.9

In [34]:

# 返回的结果是一个DataFrame
type(df[['year','pop']])

Out[34]:

pandas.core.frame.DataFrame

3.3 查询一行,结果是一个pd.Series

In [39]:

df.loc[0]

Out[39]:

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

In [40]:

type(df.loc[0])

Out[40]:

pandas.core.series.Series

3.4 查询多行,结果是一个pd.DataFrame

In [41]:

# DataFrame中切片会返回结尾的数据
df.loc[0:3]

Out[41]:

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

In [42]:

type(df.loc[0:3])

Out[42]:

pandas.core.frame.DataFrame

三.Pandas查询数据的5种方法

Pandas查询数据的几种方法

  1. df.loc方法,根据行,列的标签值查询
  2. df.iloc方法,根据行,列的数字位置查询
  3. df.where方法
  4. df.query方法

.loc方法既能查询,又能覆盖写入,推荐使用此方法

Pandas使用df.loc查询数据的方法

  1. 使用单个label值查询数据
  2. 使用值列表批量查询
  3. 使用数值区间进行范围查询
  4. 使用条件表达式查询
  5. 调用函数查询

注意

  • 以上查询方法,既适用于行,也适用于列

In [3]:

import pandas as pd

0. 读取数据

数据为北京2018年全年天气预报

In [4]:

df = pd.read_csv("./pandas-learn-code/datas/beijing_tianqi/beijing_tianqi_2018.csv")

In [5]:

df.head()

Out[5]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3℃ -6℃ 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2℃ -5℃ 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2℃ -5℃ 多云 北风 1-2级 28 1
3 2018-01-04 0℃ -8℃ 东北风 1-2级 28 1
4 2018-01-05 3℃ -6℃ 多云~晴 西北风 1-2级 50 1

In [6]:

# 设定索引为日期,方便按日期筛选
df.set_index('ymd', inplace=True)

In [7]:

df.head()

Out[7]:

bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
ymd
2018-01-01 3℃ -6℃ 晴~多云 东北风 1-2级 59 2
2018-01-02 2℃ -5℃ 阴~多云 东北风 1-2级 49 1
2018-01-03 2℃ -5℃ 多云 北风 1-2级 28 1
2018-01-04 0℃ -8℃ 东北风 1-2级 28 1
2018-01-05 3℃ -6℃ 多云~晴 西北风 1-2级 50 1

In [8]:

# 时间序列见后续课程,本次按字符串处理
df.index

Out[8]:

Index(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04', '2018-01-05',
       '2018-01-06', '2018-01-07', '2018-01-08', '2018-01-09', '2018-01-10',
       ...
       '2018-12-22', '2018-12-23', '2018-12-24', '2018-12-25', '2018-12-26',
       '2018-12-27', '2018-12-28', '2018-12-29', '2018-12-30', '2018-12-31'],
      dtype='object', name='ymd', length=365)

In [9]:

# 替换掉温度的后缀℃
# df.loc[:]表示筛选出所有的行
df.loc[:, "bWendu"] = df["bWendu"].str.replace("℃","").astype('int32')
df.loc[:, "yWendu"] = df["yWendu"].str.replace("℃","").astype('int32')

In [10]:

# bWendu和yWendu改为int类型
df.dtypes

Out[10]:

bWendu        int32
yWendu        int32
tianqi       object
fengxiang    object
fengli       object
aqi           int64
aqiInfo      object
aqiLevel      int64
dtype: object

1. 使用单个label值查询数据

行或者列,都可以只传入单个值,实现精确匹配

In [11]:

# 得到单个值
df.loc['2018-01-03','bWendu']

Out[11]:

2

In [12]:

# 得到一个Series
df.loc['2018-01-03',['bWendu', 'yWendu']]

Out[12]:

bWendu     2
yWendu    -5
Name: 2018-01-03, dtype: object

2. 使用值列进行表批量查询

In [13]:

# 得到Series
df.loc[['2018-01-03','2018-01-04','2018-01-05'], 'bWendu']

Out[13]:

ymd
2018-01-03    2
2018-01-04    0
2018-01-05    3
Name: bWendu, dtype: int32

In [14]:

# 得到DataFrame
df.loc[['2018-01-03','2018-01-04','2018-01-05'], ['bWendu','yWendu']]

Out[14]:

bWendu yWendu
ymd
2018-01-03 2 -5
2018-01-04 0 -8
2018-01-05 3 -6

3. 使用数值区间进行范围查询

注意:区间既包含开始,也包含结束

In [15]:

# 行index按区间
df.loc['2018-01-03':'2018-01-05', 'bWendu']

Out[15]:

ymd
2018-01-03    2
2018-01-04    0
2018-01-05    3
Name: bWendu, dtype: int32

In [16]:

# 列index按区间
df.loc['2018-01-03','bWendu':'fengxiang']

Out[16]:

bWendu        2
yWendu       -5
tianqi       多云
fengxiang    北风
Name: 2018-01-03, dtype: object

In [17]:

# 行和列都按区间查询
df.loc['2018-01-03':'2018-01-05','bWendu':'fengxiang']

Out[17]:

bWendu yWendu tianqi fengxiang
ymd
2018-01-03 2 -5 多云 北风
2018-01-04 0 -8 东北风
2018-01-05 3 -6 多云~晴 西北风

4. 使用条件表达式查询

bool列表的长度得等于行数或者列数

In [23]:

df.loc[df["yWendu"]<-10,:]

Out[23]:

bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
ymd
2018-01-23 -4 -12 西北风 3-4级 31 1
2018-01-24 -4 -11 西南风 1-2级 34 1
2018-01-25 -3 -11 多云 东北风 1-2级 27 1
2018-12-26 -2 -11 晴~多云 东北风 2级 26 1
2018-12-27 -5 -12 多云~晴 西北风 3级 48 1
2018-12-28 -3 -11 西北风 3级 40 1
2018-12-29 -3 -12 西北风 2级 29 1
2018-12-30 -2 -11 晴~多云 东北风 1级 31 1

In [24]:

df["yWendu"]<-10

Out[24]:

ymd
2018-01-01    False
2018-01-02    False
2018-01-03    False
2018-01-04    False
2018-01-05    False
2018-01-06    False
2018-01-07    False
2018-01-08    False
2018-01-09    False
2018-01-10    False
2018-01-11    False
2018-01-12    False
2018-01-13    False
2018-01-14    False
2018-01-15    False
2018-01-16    False
2018-01-17    False
2018-01-18    False
2018-01-19    False
2018-01-20    False
2018-01-21    False
2018-01-22    False
2018-01-23     True
2018-01-24     True
2018-01-25     True
2018-01-26    False
2018-01-27    False
2018-01-28    False
2018-01-29    False
2018-01-30    False
              ...  
2018-12-02    False
2018-12-03    False
2018-12-04    False
2018-12-05    False
2018-12-06    False
2018-12-07    False
2018-12-08    False
2018-12-09    False
2018-12-10    False
2018-12-11    False
2018-12-12    False
2018-12-13    False
2018-12-14    False
2018-12-15    False
2018-12-16    False
2018-12-17    False
2018-12-18    False
2018-12-19    False
2018-12-20    False
2018-12-21    False
2018-12-22    False
2018-12-23    False
2018-12-24    False
2018-12-25    False
2018-12-26     True
2018-12-27     True
2018-12-28     True
2018-12-29     True
2018-12-30     True
2018-12-31    False
Name: yWendu, Length: 365, dtype: bool

复杂条件查询,查询一下完美得天气

  • 注意,组合条件用&符号合并,每个条件判断都得带括号

In [29]:

df.loc[(df["bWendu"]<=30) & (df["yWendu"]>=15) & (df["tianqi"]=="晴") & (df["aqiLevel"]==1),:]

Out[29]:

bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
ymd
2018-08-24 30 20 北风 1-2级 40 1
2018-09-07 27 16 西北风 3-4级 22 1

In [30]:

(df["bWendu"]<=30) & (df["yWendu"]>=15) & (df["tianqi"]=="晴") & (df["aqiLevel"]==1)

Out[30]:

ymd
2018-01-01    False
2018-01-02    False
2018-01-03    False
2018-01-04    False
2018-01-05    False
2018-01-06    False
2018-01-07    False
2018-01-08    False
2018-01-09    False
2018-01-10    False
2018-01-11    False
2018-01-12    False
2018-01-13    False
2018-01-14    False
2018-01-15    False
2018-01-16    False
2018-01-17    False
2018-01-18    False
2018-01-19    False
2018-01-20    False
2018-01-21    False
2018-01-22    False
2018-01-23    False
2018-01-24    False
2018-01-25    False
2018-01-26    False
2018-01-27    False
2018-01-28    False
2018-01-29    False
2018-01-30    False
              ...  
2018-12-02    False
2018-12-03    False
2018-12-04    False
2018-12-05    False
2018-12-06    False
2018-12-07    False
2018-12-08    False
2018-12-09    False
2018-12-10    False
2018-12-11    False
2018-12-12    False
2018-12-13    False
2018-12-14    False
2018-12-15    False
2018-12-16    False
2018-12-17    False
2018-12-18    False
2018-12-19    False
2018-12-20    False
2018-12-21    False
2018-12-22    False
2018-12-23    False
2018-12-24    False
2018-12-25    False
2018-12-26    False
2018-12-27    False
2018-12-28    False
2018-12-29    False
2018-12-30    False
2018-12-31    False
Length: 365, dtype: bool

5. 调用函数查询

In [31]:

# 直接写lambda表达式
df.loc[lambda df: (df["bWendu"]<=30) & (df["yWendu"]>=15),:]

Out[31]:

bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
ymd
2018-04-28 27 17 西南风 3-4级 125 轻度污染 3
2018-04-29 30 16 多云 南风 3-4级 193 中度污染 4
2018-05-04 27 16 晴~多云 西南风 1-2级 86 2
2018-05-09 29 17 晴~多云 西南风 3-4级 79 2
2018-05-10 26 18 多云 南风 3-4级 118 轻度污染 3
2018-05-11 24 15 阴~多云 东风 1-2级 106 轻度污染 3
2018-05-12 28 16 小雨 东南风 3-4级 186 中度污染 4
2018-05-13 30 17 南风 1-2级 68 2
2018-05-16 29 21 多云~小雨 东风 1-2级 142 轻度污染 3
2018-05-17 25 19 小雨~多云 北风 1-2级 70 2
2018-05-18 28 16 多云~晴 南风 1-2级 49 1
2018-05-19 27 16 多云~小雨 南风 1-2级 69 2
2018-05-20 21 16 阴~小雨 东风 1-2级 54 2
2018-05-23 29 15 西南风 3-4级 153 中度污染 4
2018-05-26 30 17 小雨~多云 西南风 3-4级 143 轻度污染 3
2018-05-28 30 16 西北风 4-5级 178 中度污染 4
2018-06-09 23 17 小雨 北风 1-2级 45 1
2018-06-10 27 17 多云 东南风 1-2级 51 2
2018-06-11 29 19 多云 西南风 3-4级 85 2
2018-06-13 28 19 雷阵雨~多云 东北风 1-2级 73 2
2018-06-18 30 21 雷阵雨 西南风 1-2级 112 轻度污染 3
2018-06-22 30 21 雷阵雨~多云 东南风 1-2级 83 2
2018-07-08 30 23 雷阵雨 南风 1-2级 73 2
2018-07-09 30 22 雷阵雨~多云 东南风 1-2级 106 轻度污染 3
2018-07-10 30 22 多云~雷阵雨 南风 1-2级 48 1
2018-07-11 25 22 雷阵雨~大雨 东北风 1-2级 44 1
2018-07-12 27 22 多云 南风 1-2级 46 1
2018-07-13 28 23 雷阵雨 东风 1-2级 60 2
2018-07-17 27 23 中雨~雷阵雨 西风 1-2级 28 1
2018-07-24 28 26 暴雨~雷阵雨 东北风 3-4级 29 1
2018-08-11 30 23 雷阵雨~中雨 东风 1-2级 60 2
2018-08-12 30 24 雷阵雨 东南风 1-2级 74 2
2018-08-14 29 24 中雨~小雨 东北风 1-2级 42 1
2018-08-16 30 21 晴~多云 东北风 1-2级 40 1
2018-08-17 30 22 多云~雷阵雨 东南风 1-2级 69 2
2018-08-18 28 23 小雨~中雨 北风 3-4级 40 1
2018-08-19 26 23 中雨~小雨 东北风 1-2级 37 1
2018-08-22 28 21 雷阵雨~多云 西南风 1-2级 48 1
2018-08-24 30 20 北风 1-2级 40 1
2018-08-27 30 22 多云~雷阵雨 东南风 1-2级 89 2
2018-08-28 29 22 小雨~多云 南风 1-2级 58 2
2018-08-30 29 20 多云 南风 1-2级 47 1
2018-08-31 29 20 多云~阴 东南风 1-2级 48 1
2018-09-01 27 19 阴~小雨 南风 1-2级 50 1
2018-09-02 27 19 小雨~多云 南风 1-2级 55 2
2018-09-03 30 19 北风 3-4级 70 2
2018-09-06 27 18 多云~晴 西北风 4-5级 37 1
2018-09-07 27 16 西北风 3-4级 22 1
2018-09-08 27 15 多云~晴 北风 1-2级 28 1
2018-09-09 28 16 西南风 1-2级 51 2
2018-09-10 28 19 多云 南风 1-2级 65 2
2018-09-11 26 19 多云 南风 1-2级 68 2
2018-09-12 29 19 多云 南风 1-2级 59 2
2018-09-13 29 20 多云~阴 南风 1-2级 107 轻度污染 3
2018-09-14 28 19 小雨~多云 南风 1-2级 128 轻度污染 3
2018-09-15 26 15 多云 北风 3-4级 42 1
2018-09-17 27 17 多云~阴 北风 1-2级 37 1
2018-09-18 25 17 阴~多云 西南风 1-2级 50 1
2018-09-19 26 17 多云 南风 1-2级 52 2
2018-09-20 27 16 多云 西南风 1-2级 63 2

64 rows × 8 columns

In [33]:

# 编写自己的函数,查询9月份,空气质量好的数据
def query_my_data(df):
    return df.index.str.startswith("2018-09") & (df["aqiLevel"]==1)
df.loc[query_my_data,:]

Out[33]:

bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
ymd
2018-09-01 27 19 阴~小雨 南风 1-2级 50 1
2018-09-04 31 18 西南风 3-4级 24 1
2018-09-05 31 19 晴~多云 西南风 3-4级 34 1
2018-09-06 27 18 多云~晴 西北风 4-5级 37 1
2018-09-07 27 16 西北风 3-4级 22 1
2018-09-08 27 15 多云~晴 北风 1-2级 28 1
2018-09-15 26 15 多云 北风 3-4级 42 1
2018-09-16 25 14 多云~晴 北风 1-2级 29 1
2018-09-17 27 17 多云~阴 北风 1-2级 37 1
2018-09-18 25 17 阴~多云 西南风 1-2级 50 1
2018-09-21 25 14 西北风 3-4级 50 1
2018-09-22 24 13 西北风 3-4级 28 1
2018-09-23 23 12 西北风 4-5级 28 1
2018-09-24 23 11 北风 1-2级 28 1
2018-09-25 24 12 晴~多云 南风 1-2级 44 1
2018-09-29 22 11 北风 3-4级 21 1
2018-09-30 19 13 多云 西北风 4-5级 22 1

四、Pandas怎样新增数据列

In [1]:

import pandas as pd

0. 读取csv数据到DataFrame

In [15]:

df = pd.read_csv("./pandas-learn-code/datas/beijing_tianqi/beijing_tianqi_2018.csv")

In [16]:

df.head()

Out[16]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3℃ -6℃ 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2℃ -5℃ 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2℃ -5℃ 多云 北风 1-2级 28 1
3 2018-01-04 0℃ -8℃ 东北风 1-2级 28 1
4 2018-01-05 3℃ -6℃ 多云~晴 西北风 1-2级 50 1

1. 直接赋值的方法

实例:清理温度列,变成数字类型

In [31]:

df.loc[:,"bWendu"] = df["bWendu"].str.replace("℃","").astype('int32')
df.loc[:,"yWendu"] = df["yWendu"].str.replace("℃","").astype('int32')
实例:计算温差

In [49]:

del df["bWendnu"]

In [51]:

del df["bWednu"]

In [52]:

# 注意,fpath["bWendu"]其实是一个Series,后面的减法返回的是Series
df.loc[:,"wencha"] = df["bWendu"] - df["yWendu"]

In [53]:

df.head()

Out[53]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel wencha
0 2018-01-01 3 -6 晴~多云 东北风 1-2级 59 2 9
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1 7
2 2018-01-03 2 -5 多云 北风 1-2级 28 1 7
3 2018-01-04 0 -8 东北风 1-2级 28 1 8
4 2018-01-05 3 -6 多云~晴 西北风 1-2级 50 1 9

2. df.apply方法

Apply a function along an axis of the DataFrame. Objects passed to the function are Series objects whose index is either the DataFrame’s index (axis=0) or the DataFrame’s columns (axis=1) 实例:添加一列温度类型:

  1. 如果最高温度大于33度就是高温
  2. 低于-10度是低温
  3. 否则是常温

In [60]:

def get_wendu_type(x):
    if x["bWendu"] > 33:
        return "高温"
    if x["yWendu"] < -10:
        return "低温"
    return "常温"

# 注意需要设置axis=1
df.loc[:,"wendu_type"] = df.apply(get_wendu_type, axis=1)

In [61]:

# 查看温度类型的计数
df["wendu_type"].value_counts()

Out[61]:

常温    328
高温     29
低温      8
Name: wendu_type, dtype: int64

3. df.assign方法

Assign new columns to a DataFrame.

Returns a new object with all original columns in addtion to new ones.

实例:将温度从摄氏度变成华氏度

In [63]:

# 可以同时添加多个新的列
df.assign(
    yWendu_huashi = lambda x: x["yWendu"]*9/5 + 32,
    bWendu_huashi = lambda x: x["bWendu"]*9/5 + 32
)

. . .

4. 按条件选择分组分别赋值

按条件选择数据,然后对整个数据赋值新列

实例:高低温差大于10度,则认为温差大

In [65]:

df.loc[:,"wencha_type"] = ""
df.loc[df["bWendu"]-df["yWendu"]>10, "wencha_type"] = "温差大"
df.loc[df["bWendu"]-df["yWendu"]<=10, "wencha_type"]= "温度正常"

In [67]:

df["wencha_type"].value_counts()

Out[67]:

温度正常    187
温差大     178
Name: wencha_type, dtype: int64

五、Pandas数据统计函数

  1. 汇总类统计
  2. 唯一去重和按值计数
  3. 相关系数和协方差

In [2]:

import pandas as pd

0. 读取csv数据

In [5]:

fpath = "./pandas-learn-code/datas/beijing_tianqi/beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)

In [6]:

df.head(3)

Out[6]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3℃ -6℃ 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2℃ -5℃ 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2℃ -5℃ 多云 北风 1-2级 28 1

In [12]:

df.loc[:, "yWendu"] = df["yWendu"].str.replace("℃","").astype("int32")

In [14]:

df.head(3)

Out[14]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3 -6 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2 -5 多云 北风 1-2级 28 1

1. 汇总类统计

In [15]:

# 一次提取所有数字列统计结果
df.describe()

Out[15]:

bWendu yWendu aqi aqiLevel
count 365.000000 365.000000 365.000000 365.000000
mean 18.665753 8.358904 82.183562 2.090411
std 11.858046 11.755053 51.936159 1.029798
min -5.000000 -12.000000 21.000000 1.000000
25% 8.000000 -3.000000 46.000000 1.000000
50% 21.000000 8.000000 69.000000 2.000000
75% 29.000000 19.000000 104.000000 3.000000
max 38.000000 27.000000 387.000000 6.000000

In [16]:

#  查看单个Series的数据
df["bWendu"].mean()

Out[16]:

18.665753424657535

In [17]:

#  最高温
df["bWendu"].max()

Out[17]:

38

In [18]:

# 最低温
df["bWendu"].min()

Out[18]:

-5

2. 唯一去重和按值计数

2.1 唯一去重

一般不用于数值列,而是枚举、分类列

In [19]:

df["fengxiang"].unique()

Out[19]:

array(['东北风', '北风', '西北风', '西南风', '南风', '东南风', '东风', '西风'], dtype=object)

In [20]:

df["tianqi"].unique()

Out[20]:

array(['晴~多云', '阴~多云', '多云', '阴', '多云~晴', '多云~阴', '晴', '阴~小雪', '小雪~多云',
       '小雨~阴', '小雨~雨夹雪', '多云~小雨', '小雨~多云', '大雨~小雨', '小雨', '阴~小雨',
       '多云~雷阵雨', '雷阵雨~多云', '阴~雷阵雨', '雷阵雨', '雷阵雨~大雨', '中雨~雷阵雨', '小雨~大雨',
       '暴雨~雷阵雨', '雷阵雨~中雨', '小雨~雷阵雨', '雷阵雨~阴', '中雨~小雨', '小雨~中雨', '雾~多云',
       '霾'], dtype=object)

In [22]:

df["fengli"].unique()

Out[22]:

array(['1-2级', '4-5级', '3-4级', '2级', '1级', '3级'], dtype=object)

2.2 按值计数

In [24]:

df["fengxiang"].value_counts()

Out[24]:

南风     92
西南风    64
北风     54
西北风    51
东南风    46
东北风    38
东风     14
西风      6
Name: fengxiang, dtype: int64

In [25]:

df["tianqi"].unique()

Out[25]:

array(['晴~多云', '阴~多云', '多云', '阴', '多云~晴', '多云~阴', '晴', '阴~小雪', '小雪~多云',
       '小雨~阴', '小雨~雨夹雪', '多云~小雨', '小雨~多云', '大雨~小雨', '小雨', '阴~小雨',
       '多云~雷阵雨', '雷阵雨~多云', '阴~雷阵雨', '雷阵雨', '雷阵雨~大雨', '中雨~雷阵雨', '小雨~大雨',
       '暴雨~雷阵雨', '雷阵雨~中雨', '小雨~雷阵雨', '雷阵雨~阴', '中雨~小雨', '小雨~中雨', '雾~多云',
       '霾'], dtype=object)

In [26]:

df["fengli"].value_counts()

Out[26]:

1-2级    236
3-4级     68
1级       21
4-5级     20
2级       13
3级        7
Name: fengli, dtype: int64

3. 相关系数和协防差

用途:

  1. 两只股票,是不是同涨同跌?程度多大?正相关还是负相关?
  2. 产品销量的波动,跟哪些因素正相关、负相关,程度有多大?

对于两个变量x, y:

  1. 协方差:衡量同向反向程度,如果协方差为正,说明x,y同向变化,协方差越大说明同向程度越高;如果协方差为负,说明x,y反向运动,协方差越小说明反向程度越高。
  2. 相关系数:衡量相似度程度,当他们的相关系数为1时,说明两个变量变化时正向相似度越大,当关系数为-1时,说明两个变量变化的反向相似度最大

In [27]:

# 协方差矩阵
df.cov()

Out[27]:

bWendu yWendu aqi aqiLevel
bWendu 140.613247 135.529633 47.462622 0.879204
yWendu 135.529633 138.181274 16.186685 0.264165
aqi 47.462622 16.186685 2697.364564 50.749842
aqiLevel 0.879204 0.264165 50.749842 1.060485

In [28]:

# 相关系数矩阵
df.corr()

Out[28]:

bWendu yWendu aqi aqiLevel
bWendu 1.000000 0.972292 0.077067 0.071999
yWendu 0.972292 1.000000 0.026513 0.021822
aqi 0.077067 0.026513 1.000000 0.948883
aqiLevel 0.071999 0.021822 0.948883 1.000000

In [29]:

# 单独查看空气质量和最高温度的相关系数
df["aqi"].corr(df["bWendu"])

Out[29]:

0.07706705916811067

In [30]:

df["aqi"].corr(df["yWendu"])

Out[30]:

0.026513282672968895

In [31]:

# 空气质量和温差的相关系数
df["aqi"].corr(df["bWendu"]-df["yWendu"])

Out[31]:

0.2165225757638205
  • 虽然单独观察最高温度和最低温度对空气质量的影响不大,但是明显温差对空气质量的影响要大得多,因此,前端数据的挖掘对结果的呈现十分重要。

六、Pandas对缺失值的处理

Pandas使用这些函数处理缺失值:

  • isnull和notnull:检测是否是空值,可用于df和Series
  • dropna:丢弃、删除缺失值
    • axis:删除行还是列,{0 or “index”, 1 or “columns”}, default 0
    • how:如果等于any则任何值为空都删除,如果等于all则所有值都为空才删除
    • inplace:如果为True则修改当前df,否则返回新的df
  • fillna:填充空值
    • value:用于填充的值,可以是单个值,或者字典(key是列名,value是值)
    • method:等于ffill使用前一个不为空的值填充forward fill,等于fill使用后一个不为 空的值填充backword fill
    • axis:按行还是列填充,{0 or “index”, 1 or "columns’}
    • inplace:如果为True则修改当前df,否则返回新的df

In [1]:

import pandas as pd

实例:特殊excel的读取、清洗、处理

步骤1:读取excel的时候,忽略前几个空行

In [5]:

# skiprows=2, 跳过前两行
studf = pd.read_excel("./pandas-learn-code/datas/student_excel/student_excel.xlsx", skiprows=2)

In [6]:

studf

Out[6]:

Unnamed: 0 姓名 科目 分数
0 NaN 小明 语文 85.0
1 NaN NaN 数学 80.0
2 NaN NaN 英语 90.0
3 NaN NaN NaN NaN
4 NaN 小王 语文 85.0
5 NaN NaN 数学 NaN
6 NaN NaN 英语 90.0
7 NaN NaN NaN NaN
8 NaN 小刚 语文 85.0
9 NaN NaN 数学 80.0
10 NaN NaN 英语 90.0

步骤2:检测空值

In [7]:

studf.isnull()

Out[7]:

Unnamed: 0 姓名 科目 分数
0 True False False False
1 True True False False
2 True True False False
3 True True True True
4 True False False False
5 True True False True
6 True True False False
7 True True True True
8 True False False False
9 True True False False
10 True True False False

In [9]:

studf["分数"].isnull()

Out[9]:

0     False
1     False
2     False
3      True
4     False
5      True
6     False
7      True
8     False
9     False
10    False
Name: 分数, dtype: bool

In [10]:

studf["分数"].notnull()

Out[10]:

0      True
1      True
2      True
3     False
4      True
5     False
6      True
7     False
8      True
9      True
10     True
Name: 分数, dtype: bool

In [12]:

# 筛选没有空分数的所有行
studf.loc[studf["分数"].notnull(), :]

Out[12]:

Unnamed: 0 姓名 科目 分数
0 NaN 小明 语文 85.0
1 NaN NaN 数学 80.0
2 NaN NaN 英语 90.0
4 NaN 小王 语文 85.0
6 NaN NaN 英语 90.0
8 NaN 小刚 语文 85.0
9 NaN NaN 数学 80.0
10 NaN NaN 英语 90.0

步骤3:删除掉全是空值的列

In [15]:

studf.dropna(axis="columns", how="all", inplace=True)

In [16]:

studf

Out[16]:

姓名 科目 分数
0 小明 语文 85.0
1 NaN 数学 80.0
2 NaN 英语 90.0
4 小王 语文 85.0
5 NaN 数学 NaN
6 NaN 英语 90.0
8 小刚 语文 85.0
9 NaN 数学 80.0
10 NaN 英语 90.0

步骤4:删除掉全是空值的行

In [13]:

studf.dropna(axis="index", how="all", inplace=True)

In [17]:

studf

Out[17]:

姓名 科目 分数
0 小明 语文 85.0
1 NaN 数学 80.0
2 NaN 英语 90.0
4 小王 语文 85.0
5 NaN 数学 NaN
6 NaN 英语 90.0
8 小刚 语文 85.0
9 NaN 数学 80.0
10 NaN 英语 90.0

步骤5:将分数列为空的填充为0分

In [19]:

studf.fillna({"分数": 0})

. . .

In [20]:

# 等同于
studf.loc[:,"分数"] = studf["分数"].fillna(0)

In [21]:

studf

Out[21]:

姓名 科目 分数
0 小明 语文 85.0
1 NaN 数学 80.0
2 NaN 英语 90.0
4 小王 语文 85.0
5 NaN 数学 0.0
6 NaN 英语 90.0
8 小刚 语文 85.0
9 NaN 数学 80.0
10 NaN 英语 90.0

步骤6:将姓名的缺失值填充

使用前面的有效值填充,用ffill:forward fill

In [22]:

studf.loc[:, "姓名"] = studf['姓名'].fillna(method="ffill")

In [23]:

studf

Out[23]:

姓名 科目 分数
0 小明 语文 85.0
1 小明 数学 80.0
2 小明 英语 90.0
4 小王 语文 85.0
5 小王 数学 0.0
6 小王 英语 90.0
8 小刚 语文 85.0
9 小刚 数学 80.0
10 小刚 英语 90.0

步骤7:将清晰好的excel保存

In [25]:

studf.to_excel(r"D:\WinterIsComing\python\New_Wave\pandas_basic\student_excel.xlsx", index=False)

七、Pandas的SettingWithCopyWarning报警

0. 数据读取

In [1]:

import pandas as pd

In [2]:

fpath = "./pandas-learn-code/datas/beijing_tianqi/beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)

In [3]:

df.head()

Out[3]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3℃ -6℃ 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2℃ -5℃ 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2℃ -5℃ 多云 北风 1-2级 28 1
3 2018-01-04 0℃ -8℃ 东北风 1-2级 28 1
4 2018-01-05 3℃ -6℃ 多云~晴 西北风 1-2级 50 1

In [5]:

df.loc[:, "bWendu"] = df["bWendu"].str.replace("℃","").astype("int32")
df.loc[:, "yWendu"] = df["yWendu"].str.replace("℃","").astype("int32")

In [7]:

df.head()

Out[7]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3 -6 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2 -5 多云 北风 1-2级 28 1
3 2018-01-04 0 -8 东北风 1-2级 28 1
4 2018-01-05 3 -6 多云~晴 西北风 1-2级 50 1

1. 复现

In [10]:

# 筛选出3月份的数据用于分析
condition = df.loc[:, "ymd"].str.startswith("2018-03")

In [11]:

# 设置三月份的温差
# 错误写法
df[condition]["wen_cha"] = df["bWendu"] - df["yWendu"]
D:\Tools\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  This is separate from the ipykernel package so we can avoid doing imports until

In [12]:

# 查看修改是否成功
df[condition].head()
# 只筛选了3月的数据,但没有新增温差列

Out[12]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
59 2018-03-01 8 -3 多云 西南风 1-2级 46 1
60 2018-03-02 9 -1 晴~多云 北风 1-2级 95 2
61 2018-03-03 13 3 多云~阴 北风 1-2级 214 重度污染 5
62 2018-03-04 7 -2 阴~多云 东南风 1-2级 144 轻度污染 3
63 2018-03-05 8 -3 南风 1-2级 94 2

2、原因

发出警告的代码 df[condition][“wen_cha”] = df[“bWendu”]-df[“yWendu”]

相当于:df.get(condition).set(wen_cha),第一步骤的get发出了报警

*链式操作其实是两个步骤,先get后set,get得到的dataframe可能是view(是DateFrame的子视图,我们对它修改会直接影响原DateFrame)也可能是copy,pandas发出警告*

官网文档: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

核心要诀:pandas的dataframe的修改写操作,只允许在源dataframe上进行,一步到位

## 3. 解决方法1
将get+set的两步操作,改成set的一步操作

In [15]:

df.loc[condition, "wen_cha"] = df["bWendu"] - df["yWendu"]

In [18]:

df.head(2)

Out[18]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel wen_cha
0 2018-01-01 3 -6 晴~多云 东北风 1-2级 59 2 NaN
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1 NaN

In [19]:

df[condition].head(2)

Out[19]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel wen_cha
59 2018-03-01 8 -3 多云 西南风 1-2级 46 1 11.0
60 2018-03-02 9 -1 晴~多云 北风 1-2级 95 2 10.0

4. 解决方法2

如果需要预筛选数据做后续的处理分析,先使用copy复制DataFrame并进行操作

In [20]:

# 复制一个新的DateFrame df_month3:筛选3月份的数据并复制
df_month3 = df[condition].copy()

In [22]:

df_month3.head()

Out[22]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel wen_cha
59 2018-03-01 8 -3 多云 西南风 1-2级 46 1 11.0
60 2018-03-02 9 -1 晴~多云 北风 1-2级 95 2 10.0
61 2018-03-03 13 3 多云~阴 北风 1-2级 214 重度污染 5 10.0
62 2018-03-04 7 -2 阴~多云 东南风 1-2级 144 轻度污染 3 9.0
63 2018-03-05 8 -3 南风 1-2级 94 2 11.0

In [24]:

df_month3["wencha"] = df_month3["bWendu"] - df_month3["yWendu"]

In [25]:

df_month3.head()

Out[25]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel wen_cha wencha
59 2018-03-01 8 -3 多云 西南风 1-2级 46 1 11.0 11
60 2018-03-02 9 -1 晴~多云 北风 1-2级 95 2 10.0 10
61 2018-03-03 13 3 多云~阴 北风 1-2级 214 重度污染 5 10.0 10
62 2018-03-04 7 -2 阴~多云 东南风 1-2级 144 轻度污染 3 9.0 9
63 2018-03-05 8 -3 南风 1-2级 94 2 11.0 11

Pandas不允许先筛选子DataFrame,在进行修改写入

  • 要么使用.loc实现一个步骤直接修改源dataframe
  • 要么先复制一个子DataFrame再一个步骤执行修改

八、Pandas数据排序

Series的排序:
*Series.sort_values(ascending=True, inplace=False)*
参数说明:

  • ascending:默认为True升序排序,为False降序排序
  • inplace:是否修改原始Series

DataFrame的排序:
*DataFrame.sort_values(by, ascending=True, inplace=False)*
参数说明:

  • by:字符串或者List<字符串>,单列排序或者多列排序
  • ascending:bool或者List,升序还是降序,如果是list对应by的多列
  • inplace:是否修改原始DataFrame

In [1]:

import pandas as pd

0. 读取数据

In [2]:

fpath = "./pandas-learn-code/datas/beijing_tianqi/beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)

In [4]:

# 替换温度的后缀℃
df.loc[:, "bWendu"] = df["bWendu"].str.replace("℃","").astype("int32")
df.loc[:, "yWendu"] = df["yWendu"].str.replace("℃","").astype("int32")

In [5]:

df.head()

Out[5]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3 -6 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2 -5 多云 北风 1-2级 28 1
3 2018-01-04 0 -8 东北风 1-2级 28 1
4 2018-01-05 3 -6 多云~晴 西北风 1-2级 50 1

1. Series的排序

In [7]:

# 默认为升序
df["aqi"].sort_values()

Out[7]:

271     21
281     21
249     22
272     22
301     22
246     24
35      24
33      24
10      24
273     25
282     25
359     26
9       26
111     27
24      27
2       28
264     28
319     28
250     28
266     28
3       28
265     28
205     28
197     28
204     29
258     29
362     29
283     30
308     30
22      31
      ... 
334    163
109    164
108    170
68     171
176    174
70     174
294    176
124    177
286    177
147    178
49     183
131    186
13     187
118    193
336    198
287    198
330    198
306    206
61     214
90     218
316    219
57     220
335    234
85     243
329    245
317    266
71     287
91     287
72     293
86     387
Name: aqi, Length: 365, dtype: int64

In [10]:

# 将排序方式调整为降序
df["aqi"].sort_values(ascending=False)

Out[10]:

86     387
72     293
91     287
71     287
317    266
329    245
85     243
335    234
57     220
316    219
90     218
61     214
306    206
330    198
287    198
336    198
118    193
13     187
131    186
49     183
147    178
286    177
124    177
294    176
70     174
176    174
68     171
108    170
109    164
334    163
      ... 
22      31
308     30
283     30
362     29
258     29
204     29
197     28
205     28
265     28
3       28
266     28
250     28
319     28
264     28
2       28
24      27
111     27
9       26
359     26
282     25
273     25
10      24
33      24
35      24
246     24
301     22
272     22
249     22
281     21
271     21
Name: aqi, Length: 365, dtype: int64

In [12]:

# 对中文也可以排序
df["tianqi"].sort_values()

Out[12]:

225     中雨~小雨
230     中雨~小雨
197    中雨~雷阵雨
196    中雨~雷阵雨
112        多云
108        多云
232        多云
234        多云
241        多云
94         多云
91         多云
88         多云
252        多云
84         多云
364        多云
165        多云
81         多云
79         多云
78         多云
77         多云
257        多云
74         多云
69         多云
67         多云
261        多云
262        多云
268        多云
270        多云
226        多云
253        多云
        ...  
338      阴~多云
111      阴~多云
243      阴~小雨
139      阴~小雨
20       阴~小雪
167     阴~雷阵雨
237       雷阵雨
195       雷阵雨
223       雷阵雨
187       雷阵雨
168       雷阵雨
188       雷阵雨
193       雷阵雨
175       雷阵雨
218    雷阵雨~中雨
216    雷阵雨~中雨
224    雷阵雨~中雨
222    雷阵雨~中雨
189    雷阵雨~多云
163    雷阵雨~多云
180    雷阵雨~多云
183    雷阵雨~多云
194    雷阵雨~多云
172    雷阵雨~多云
233    雷阵雨~多云
191    雷阵雨~大雨
219     雷阵雨~阴
335      雾~多云
353         霾
348         霾
Name: tianqi, Length: 365, dtype: object

2. DataFrame的排序

2.1 单列排序

In [13]:

# 按照空气质量进行排序
df.sort_values(by="aqi")

Out[13]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
271 2018-09-29 22 11 北风 3-4级 21 1
281 2018-10-09 15 4 多云~晴 西北风 4-5级 21 1
249 2018-09-07 27 16 西北风 3-4级 22 1
272 2018-09-30 19 13 多云 西北风 4-5级 22 1
301 2018-10-29 15 3 北风 3-4级 22 1
246 2018-09-04 31 18 西南风 3-4级 24 1
35 2018-02-05 0 -10 北风 3-4级 24 1
33 2018-02-03 0 -9 多云 北风 1-2级 24 1
10 2018-01-11 -1 -10 北风 1-2级 24 1
273 2018-10-01 24 12 北风 4-5级 25 1
282 2018-10-10 17 4 多云~晴 西北风 1-2级 25 1
359 2018-12-26 -2 -11 晴~多云 东北风 2级 26 1
9 2018-01-10 -2 -10 西北风 1-2级 26 1
111 2018-04-22 16 12 阴~多云 东北风 3-4级 27 1
24 2018-01-25 -3 -11 多云 东北风 1-2级 27 1
2 2018-01-03 2 -5 多云 北风 1-2级 28 1
264 2018-09-22 24 13 西北风 3-4级 28 1
319 2018-11-16 8 -1 晴~多云 北风 1-2级 28 1
250 2018-09-08 27 15 多云~晴 北风 1-2级 28 1
266 2018-09-24 23 11 北风 1-2级 28 1
3 2018-01-04 0 -8 东北风 1-2级 28 1
265 2018-09-23 23 12 西北风 4-5级 28 1
205 2018-07-25 32 25 多云 北风 1-2级 28 1
197 2018-07-17 27 23 中雨~雷阵雨 西风 1-2级 28 1
204 2018-07-24 28 26 暴雨~雷阵雨 东北风 3-4级 29 1
258 2018-09-16 25 14 多云~晴 北风 1-2级 29 1
362 2018-12-29 -3 -12 西北风 2级 29 1
283 2018-10-11 18 5 晴~多云 北风 1-2级 30 1
308 2018-11-05 10 2 多云 西南风 1-2级 30 1
22 2018-01-23 -4 -12 西北风 3-4级 31 1
334 2018-12-01 7 0 多云 东南风 1级 163 中度污染 4
109 2018-04-20 28 14 多云~小雨 南风 4-5级 164 中度污染 4
108 2018-04-19 26 13 多云 东南风 4-5级 170 中度污染 4
68 2018-03-10 14 -2 东南风 1-2级 171 中度污染 4
176 2018-06-26 36 25 西南风 3-4级 174 中度污染 4
70 2018-03-12 15 3 多云~晴 南风 1-2级 174 中度污染 4
294 2018-10-22 19 5 多云~晴 西北风 1-2级 176 中度污染 4
124 2018-05-05 25 13 多云 北风 3-4级 177 中度污染 4
286 2018-10-14 21 10 多云 南风 1-2级 177 中度污染 4
147 2018-05-28 30 16 西北风 4-5级 178 中度污染 4
49 2018-02-19 6 -3 多云 南风 1-2级 183 中度污染 4
131 2018-05-12 28 16 小雨 东南风 3-4级 186 中度污染 4
13 2018-01-14 6 -5 晴~多云 西北风 1-2级 187 中度污染 4
118 2018-04-29 30 16 多云 南风 3-4级 193 中度污染 4
336 2018-12-03 8 -3 多云~晴 东北风 3级 198 中度污染 4
287 2018-10-15 17 11 小雨 北风 1-2级 198 中度污染 4
330 2018-11-27 9 -3 晴~多云 西北风 2级 198 中度污染 4
306 2018-11-03 16 6 多云 南风 1-2级 206 重度污染 5
61 2018-03-03 13 3 多云~阴 北风 1-2级 214 重度污染 5
90 2018-04-01 25 11 晴~多云 南风 1-2级 218 重度污染 5
316 2018-11-13 13 5 多云 东南风 1-2级 219 重度污染 5
57 2018-02-27 7 0 东风 1-2级 220 重度污染 5
335 2018-12-02 9 2 雾~多云 东北风 1级 234 重度污染 5
85 2018-03-27 27 11 南风 1-2级 243 重度污染 5
329 2018-11-26 10 0 多云 东南风 1级 245 重度污染 5
317 2018-11-14 13 5 多云 南风 1-2级 266 重度污染 5
71 2018-03-13 17 5 晴~多云 南风 1-2级 287 重度污染 5
91 2018-04-02 26 11 多云 北风 1-2级 287 重度污染 5
72 2018-03-14 15 6 多云~阴 东北风 1-2级 293 重度污染 5
86 2018-03-28 25 9 多云~晴 东风 1-2级 387 严重污染 6

365 rows × 9 columns

In [14]:

# 指定降序
df.sort_values(by="aqi", ascending=False)

Out[14]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
86 2018-03-28 25 9 多云~晴 东风 1-2级 387 严重污染 6
72 2018-03-14 15 6 多云~阴 东北风 1-2级 293 重度污染 5
71 2018-03-13 17 5 晴~多云 南风 1-2级 287 重度污染 5
91 2018-04-02 26 11 多云 北风 1-2级 287 重度污染 5
317 2018-11-14 13 5 多云 南风 1-2级 266 重度污染 5
329 2018-11-26 10 0 多云 东南风 1级 245 重度污染 5
85 2018-03-27 27 11 南风 1-2级 243 重度污染 5
335 2018-12-02 9 2 雾~多云 东北风 1级 234 重度污染 5
57 2018-02-27 7 0 东风 1-2级 220 重度污染 5
316 2018-11-13 13 5 多云 东南风 1-2级 219 重度污染 5
90 2018-04-01 25 11 晴~多云 南风 1-2级 218 重度污染 5
61 2018-03-03 13 3 多云~阴 北风 1-2级 214 重度污染 5
306 2018-11-03 16 6 多云 南风 1-2级 206 重度污染 5
287 2018-10-15 17 11 小雨 北风 1-2级 198 中度污染 4
336 2018-12-03 8 -3 多云~晴 东北风 3级 198 中度污染 4
330 2018-11-27 9 -3 晴~多云 西北风 2级 198 中度污染 4
118 2018-04-29 30 16 多云 南风 3-4级 193 中度污染 4
13 2018-01-14 6 -5 晴~多云 西北风 1-2级 187 中度污染 4
131 2018-05-12 28 16 小雨 东南风 3-4级 186 中度污染 4
49 2018-02-19 6 -3 多云 南风 1-2级 183 中度污染 4
147 2018-05-28 30 16 西北风 4-5级 178 中度污染 4
286 2018-10-14 21 10 多云 南风 1-2级 177 中度污染 4
124 2018-05-05 25 13 多云 北风 3-4级 177 中度污染 4
294 2018-10-22 19 5 多云~晴 西北风 1-2级 176 中度污染 4
70 2018-03-12 15 3 多云~晴 南风 1-2级 174 中度污染 4
176 2018-06-26 36 25 西南风 3-4级 174 中度污染 4
68 2018-03-10 14 -2 东南风 1-2级 171 中度污染 4
108 2018-04-19 26 13 多云 东南风 4-5级 170 中度污染 4
109 2018-04-20 28 14 多云~小雨 南风 4-5级 164 中度污染 4
334 2018-12-01 7 0 多云 东南风 1级 163 中度污染 4
274 2018-10-02 24 11 西北风 1-2级 31 1
308 2018-11-05 10 2 多云 西南风 1-2级 30 1
283 2018-10-11 18 5 晴~多云 北风 1-2级 30 1
362 2018-12-29 -3 -12 西北风 2级 29 1
258 2018-09-16 25 14 多云~晴 北风 1-2级 29 1
204 2018-07-24 28 26 暴雨~雷阵雨 东北风 3-4级 29 1
2 2018-01-03 2 -5 多云 北风 1-2级 28 1
3 2018-01-04 0 -8 东北风 1-2级 28 1
250 2018-09-08 27 15 多云~晴 北风 1-2级 28 1
205 2018-07-25 32 25 多云 北风 1-2级 28 1
197 2018-07-17 27 23 中雨~雷阵雨 西风 1-2级 28 1
264 2018-09-22 24 13 西北风 3-4级 28 1
266 2018-09-24 23 11 北风 1-2级 28 1
265 2018-09-23 23 12 西北风 4-5级 28 1
319 2018-11-16 8 -1 晴~多云 北风 1-2级 28 1
111 2018-04-22 16 12 阴~多云 东北风 3-4级 27 1
24 2018-01-25 -3 -11 多云 东北风 1-2级 27 1
9 2018-01-10 -2 -10 西北风 1-2级 26 1
359 2018-12-26 -2 -11 晴~多云 东北风 2级 26 1
273 2018-10-01 24 12 北风 4-5级 25 1
282 2018-10-10 17 4 多云~晴 西北风 1-2级 25 1
33 2018-02-03 0 -9 多云 北风 1-2级 24 1
246 2018-09-04 31 18 西南风 3-4级 24 1
10 2018-01-11 -1 -10 北风 1-2级 24 1
35 2018-02-05 0 -10 北风 3-4级 24 1
249 2018-09-07 27 16 西北风 3-4级 22 1
301 2018-10-29 15 3 北风 3-4级 22 1
272 2018-09-30 19 13 多云 西北风 4-5级 22 1
271 2018-09-29 22 11 北风 3-4级 21 1
281 2018-10-09 15 4 多云~晴 西北风 4-5级 21 1

365 rows × 9 columns

2.2 多列排序

In [15]:

# 按空气质量等级、最高温度默认排序,默认升序
df.sort_values(by=["aqiLevel", "bWendu"])

Out[15]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
360 2018-12-27 -5 -12 多云~晴 西北风 3级 48 1
22 2018-01-23 -4 -12 西北风 3-4级 31 1
23 2018-01-24 -4 -11 西南风 1-2级 34 1
340 2018-12-07 -4 -10 西北风 3级 33 1
21 2018-01-22 -3 -10 小雪~多云 东风 1-2级 47 1
24 2018-01-25 -3 -11 多云 东北风 1-2级 27 1
25 2018-01-26 -3 -10 晴~多云 南风 1-2级 39 1
361 2018-12-28 -3 -11 西北风 3级 40 1
362 2018-12-29 -3 -12 西北风 2级 29 1
9 2018-01-10 -2 -10 西北风 1-2级 26 1
339 2018-12-06 -2 -9 西北风 3级 40 1
341 2018-12-08 -2 -10 晴~多云 西北风 2级 37 1
359 2018-12-26 -2 -11 晴~多云 东北风 2级 26 1
363 2018-12-30 -2 -11 晴~多云 东北风 1级 31 1
10 2018-01-11 -1 -10 北风 1-2级 24 1
32 2018-02-02 -1 -9 北风 3-4级 32 1
3 2018-01-04 0 -8 东北风 1-2级 28 1
33 2018-02-03 0 -9 多云 北风 1-2级 24 1
35 2018-02-05 0 -10 北风 3-4级 24 1
8 2018-01-09 1 -8 西北风 3-4级 34 1
34 2018-02-04 1 -8 西南风 1-2级 36 1
40 2018-02-10 1 -9 西北风 3-4级 39 1
345 2018-12-12 1 -8 西南风 1级 50 1
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2 -5 多云 北风 1-2级 28 1
5 2018-01-06 2 -5 多云~阴 西南风 1-2级 32 1
7 2018-01-08 2 -6 西北风 4-5级 50 1
14 2018-01-15 2 -5 东南风 1-2级 47 1
4 2018-01-05 3 -6 多云~晴 西北风 1-2级 50 1
346 2018-12-13 3 -7 西北风 2级 42 1
330 2018-11-27 9 -3 晴~多云 西北风 2级 198 中度污染 4
56 2018-02-26 12 -1 晴~多云 西南风 1-2级 157 中度污染 4
68 2018-03-10 14 -2 东南风 1-2级 171 中度污染 4
70 2018-03-12 15 3 多云~晴 南风 1-2级 174 中度污染 4
287 2018-10-15 17 11 小雨 北风 1-2级 198 中度污染 4
294 2018-10-22 19 5 多云~晴 西北风 1-2级 176 中度污染 4
286 2018-10-14 21 10 多云 南风 1-2级 177 中度污染 4
84 2018-03-26 25 7 多云 西南风 1-2级 151 中度污染 4
124 2018-05-05 25 13 多云 北风 3-4级 177 中度污染 4
108 2018-04-19 26 13 多云 东南风 4-5级 170 中度污染 4
109 2018-04-20 28 14 多云~小雨 南风 4-5级 164 中度污染 4
131 2018-05-12 28 16 小雨 东南风 3-4级 186 中度污染 4
142 2018-05-23 29 15 西南风 3-4级 153 中度污染 4
118 2018-04-29 30 16 多云 南风 3-4级 193 中度污染 4
147 2018-05-28 30 16 西北风 4-5级 178 中度污染 4
133 2018-05-14 34 22 晴~多云 南风 3-4级 158 中度污染 4
176 2018-06-26 36 25 西南风 3-4级 174 中度污染 4
57 2018-02-27 7 0 东风 1-2级 220 重度污染 5
335 2018-12-02 9 2 雾~多云 东北风 1级 234 重度污染 5
329 2018-11-26 10 0 多云 东南风 1级 245 重度污染 5
61 2018-03-03 13 3 多云~阴 北风 1-2级 214 重度污染 5
316 2018-11-13 13 5 多云 东南风 1-2级 219 重度污染 5
317 2018-11-14 13 5 多云 南风 1-2级 266 重度污染 5
72 2018-03-14 15 6 多云~阴 东北风 1-2级 293 重度污染 5
306 2018-11-03 16 6 多云 南风 1-2级 206 重度污染 5
71 2018-03-13 17 5 晴~多云 南风 1-2级 287 重度污染 5
90 2018-04-01 25 11 晴~多云 南风 1-2级 218 重度污染 5
91 2018-04-02 26 11 多云 北风 1-2级 287 重度污染 5
85 2018-03-27 27 11 南风 1-2级 243 重度污染 5
86 2018-03-28 25 9 多云~晴 东风 1-2级 387 严重污染 6

365 rows × 9 columns

In [17]:

# 两个字段都是降序
df.sort_values(by=["aqiLevel","bWendu"],ascending=False)

Out[17]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
86 2018-03-28 25 9 多云~晴 东风 1-2级 387 严重污染 6
85 2018-03-27 27 11 南风 1-2级 243 重度污染 5
91 2018-04-02 26 11 多云 北风 1-2级 287 重度污染 5
90 2018-04-01 25 11 晴~多云 南风 1-2级 218 重度污染 5
71 2018-03-13 17 5 晴~多云 南风 1-2级 287 重度污染 5
306 2018-11-03 16 6 多云 南风 1-2级 206 重度污染 5
72 2018-03-14 15 6 多云~阴 东北风 1-2级 293 重度污染 5
61 2018-03-03 13 3 多云~阴 北风 1-2级 214 重度污染 5
316 2018-11-13 13 5 多云 东南风 1-2级 219 重度污染 5
317 2018-11-14 13 5 多云 南风 1-2级 266 重度污染 5
329 2018-11-26 10 0 多云 东南风 1级 245 重度污染 5
335 2018-12-02 9 2 雾~多云 东北风 1级 234 重度污染 5
57 2018-02-27 7 0 东风 1-2级 220 重度污染 5
176 2018-06-26 36 25 西南风 3-4级 174 中度污染 4
133 2018-05-14 34 22 晴~多云 南风 3-4级 158 中度污染 4
118 2018-04-29 30 16 多云 南风 3-4级 193 中度污染 4
147 2018-05-28 30 16 西北风 4-5级 178 中度污染 4
142 2018-05-23 29 15 西南风 3-4级 153 中度污染 4
109 2018-04-20 28 14 多云~小雨 南风 4-5级 164 中度污染 4
131 2018-05-12 28 16 小雨 东南风 3-4级 186 中度污染 4
108 2018-04-19 26 13 多云 东南风 4-5级 170 中度污染 4
84 2018-03-26 25 7 多云 西南风 1-2级 151 中度污染 4
124 2018-05-05 25 13 多云 北风 3-4级 177 中度污染 4
286 2018-10-14 21 10 多云 南风 1-2级 177 中度污染 4
294 2018-10-22 19 5 多云~晴 西北风 1-2级 176 中度污染 4
287 2018-10-15 17 11 小雨 北风 1-2级 198 中度污染 4
70 2018-03-12 15 3 多云~晴 南风 1-2级 174 中度污染 4
68 2018-03-10 14 -2 东南风 1-2级 171 中度污染 4
56 2018-02-26 12 -1 晴~多云 西南风 1-2级 157 中度污染 4
330 2018-11-27 9 -3 晴~多云 西北风 2级 198 中度污染 4
4 2018-01-05 3 -6 多云~晴 西北风 1-2级 50 1
346 2018-12-13 3 -7 西北风 2级 42 1
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2 -5 多云 北风 1-2级 28 1
5 2018-01-06 2 -5 多云~阴 西南风 1-2级 32 1
7 2018-01-08 2 -6 西北风 4-5级 50 1
14 2018-01-15 2 -5 东南风 1-2级 47 1
8 2018-01-09 1 -8 西北风 3-4级 34 1
34 2018-02-04 1 -8 西南风 1-2级 36 1
40 2018-02-10 1 -9 西北风 3-4级 39 1
345 2018-12-12 1 -8 西南风 1级 50 1
3 2018-01-04 0 -8 东北风 1-2级 28 1
33 2018-02-03 0 -9 多云 北风 1-2级 24 1
35 2018-02-05 0 -10 北风 3-4级 24 1
10 2018-01-11 -1 -10 北风 1-2级 24 1
32 2018-02-02 -1 -9 北风 3-4级 32 1
9 2018-01-10 -2 -10 西北风 1-2级 26 1
339 2018-12-06 -2 -9 西北风 3级 40 1
341 2018-12-08 -2 -10 晴~多云 西北风 2级 37 1
359 2018-12-26 -2 -11 晴~多云 东北风 2级 26 1
363 2018-12-30 -2 -11 晴~多云 东北风 1级 31 1
21 2018-01-22 -3 -10 小雪~多云 东风 1-2级 47 1
24 2018-01-25 -3 -11 多云 东北风 1-2级 27 1
25 2018-01-26 -3 -10 晴~多云 南风 1-2级 39 1
361 2018-12-28 -3 -11 西北风 3级 40 1
362 2018-12-29 -3 -12 西北风 2级 29 1
22 2018-01-23 -4 -12 西北风 3-4级 31 1
23 2018-01-24 -4 -11 西南风 1-2级 34 1
340 2018-12-07 -4 -10 西北风 3级 33 1
360 2018-12-27 -5 -12 多云~晴 西北风 3级 48 1

365 rows × 9 columns

In [18]:

# 分别指定升序和降序
df.sort_values(by=["aqiLevel", "bWendu"], ascending=[True, False])

Out[18]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
178 2018-06-28 35 24 多云~晴 北风 1-2级 33 1
149 2018-05-30 33 18 西风 1-2级 46 1
206 2018-07-26 33 25 多云~雷阵雨 东北风 1-2级 40 1
158 2018-06-08 32 19 多云~雷阵雨 西南风 1-2级 43 1
205 2018-07-25 32 25 多云 北风 1-2级 28 1
226 2018-08-15 32 24 多云 东北风 3-4级 33 1
231 2018-08-20 32 23 多云~晴 北风 1-2级 41 1
232 2018-08-21 32 22 多云 北风 1-2级 38 1
148 2018-05-29 31 16 多云 西北风 1-2级 41 1
196 2018-07-16 31 24 中雨~雷阵雨 南风 1-2级 43 1
234 2018-08-23 31 21 多云 北风 1-2级 43 1
240 2018-08-29 31 20 晴~多云 北风 3-4级 44 1
246 2018-09-04 31 18 西南风 3-4级 24 1
247 2018-09-05 31 19 晴~多云 西南风 3-4级 34 1
190 2018-07-10 30 22 多云~雷阵雨 南风 1-2级 48 1
220 2018-08-09 30 24 多云 南风 1-2级 49 1
227 2018-08-16 30 21 晴~多云 东北风 1-2级 40 1
235 2018-08-24 30 20 北风 1-2级 40 1
219 2018-08-08 29 24 雷阵雨~阴 东北风 1-2级 45 1
225 2018-08-14 29 24 中雨~小雨 东北风 1-2级 42 1
241 2018-08-30 29 20 多云 南风 1-2级 47 1
242 2018-08-31 29 20 多云~阴 东南风 1-2级 48 1
137 2018-05-18 28 16 多云~晴 南风 1-2级 49 1
204 2018-07-24 28 26 暴雨~雷阵雨 东北风 3-4级 29 1
229 2018-08-18 28 23 小雨~中雨 北风 3-4级 40 1
233 2018-08-22 28 21 雷阵雨~多云 西南风 1-2级 48 1
192 2018-07-12 27 22 多云 南风 1-2级 46 1
197 2018-07-17 27 23 中雨~雷阵雨 西风 1-2级 28 1
243 2018-09-01 27 19 阴~小雨 南风 1-2级 50 1
248 2018-09-06 27 18 多云~晴 西北风 4-5级 37 1
142 2018-05-23 29 15 西南风 3-4级 153 中度污染 4
109 2018-04-20 28 14 多云~小雨 南风 4-5级 164 中度污染 4
131 2018-05-12 28 16 小雨 东南风 3-4级 186 中度污染 4
108 2018-04-19 26 13 多云 东南风 4-5级 170 中度污染 4
84 2018-03-26 25 7 多云 西南风 1-2级 151 中度污染 4
124 2018-05-05 25 13 多云 北风 3-4级 177 中度污染 4
286 2018-10-14 21 10 多云 南风 1-2级 177 中度污染 4
294 2018-10-22 19 5 多云~晴 西北风 1-2级 176 中度污染 4
287 2018-10-15 17 11 小雨 北风 1-2级 198 中度污染 4
70 2018-03-12 15 3 多云~晴 南风 1-2级 174 中度污染 4
68 2018-03-10 14 -2 东南风 1-2级 171 中度污染 4
56 2018-02-26 12 -1 晴~多云 西南风 1-2级 157 中度污染 4
330 2018-11-27 9 -3 晴~多云 西北风 2级 198 中度污染 4
336 2018-12-03 8 -3 多云~晴 东北风 3级 198 中度污染 4
334 2018-12-01 7 0 多云 东南风 1级 163 中度污染 4
13 2018-01-14 6 -5 晴~多云 西北风 1-2级 187 中度污染 4
49 2018-02-19 6 -3 多云 南风 1-2级 183 中度污染 4
85 2018-03-27 27 11 南风 1-2级 243 重度污染 5
91 2018-04-02 26 11 多云 北风 1-2级 287 重度污染 5
90 2018-04-01 25 11 晴~多云 南风 1-2级 218 重度污染 5
71 2018-03-13 17 5 晴~多云 南风 1-2级 287 重度污染 5
306 2018-11-03 16 6 多云 南风 1-2级 206 重度污染 5
72 2018-03-14 15 6 多云~阴 东北风 1-2级 293 重度污染 5
61 2018-03-03 13 3 多云~阴 北风 1-2级 214 重度污染 5
316 2018-11-13 13 5 多云 东南风 1-2级 219 重度污染 5
317 2018-11-14 13 5 多云 南风 1-2级 266 重度污染 5
329 2018-11-26 10 0 多云 东南风 1级 245 重度污染 5
335 2018-12-02 9 2 雾~多云 东北风 1级 234 重度污染 5
57 2018-02-27 7 0 东风 1-2级 220 重度污染 5
86 2018-03-28 25 9 多云~晴 东风 1-2级 387 严重污染 6

365 rows × 9 columns

九、Pandas字符串处理

前面我们已经使用了字符串的处理函数:
df[“bWendu”].str.replace(“℃”, “”).astype(‘int32’)

*Pandas的字符串处理:*

  1. 使用方法:先获取Series的str属性,然后在属性上调用函数;
  2. 只能在字符串列上使用,不能数字列上使用;
  3. Dataframe上没有str属性和处理方法
  4. Series.str并不是Python原生字符串,而是自己的一套方法,不过大部分和原生str很相似;

*Series.str字符串方法列表参考文档:*
https://pandas.pydata.org/pandas-docs/stable/reference/series.html#string-handling

*本节演示内容:*

  1. 获取Series的str属性,然后使用各种字符串处理函数
  2. 使用str的startswith、contains等bool类Series可以做条件查询
  3. 需要多次str处理的链式操作
  4. 使用正则表达式的处理

0. 读取北京2018年天气数据

In [5]:

import pandas as pd

In [6]:

fpath = "./pandas-learn-code/datas/beijing_tianqi/beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)

In [8]:

df.head()

Out[8]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3℃ -6℃ 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2℃ -5℃ 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2℃ -5℃ 多云 北风 1-2级 28 1
3 2018-01-04 0℃ -8℃ 东北风 1-2级 28 1
4 2018-01-05 3℃ -6℃ 多云~晴 西北风 1-2级 50 1

In [13]:

df.dtypes

Out[13]:

ymd          object
bWendu       object
yWendu       object
tianqi       object
fengxiang    object
fengli       object
aqi           int64
aqiInfo      object
aqiLevel      int64
dtype: object

1. 获取Series的str属性,使用各种字符串处理函数

In [14]:

df["bWendu"].str

Out[14]:


In [15]:

# 字符串替换函数
df.loc[:, "bWendu"] = df["bWendu"].str.replace("℃","").astype("int32")

In [16]:

df.head()

Out[16]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3 -6℃ 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2 -5℃ 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2 -5℃ 多云 北风 1-2级 28 1
3 2018-01-04 0 -8℃ 东北风 1-2级 28 1
4 2018-01-05 3 -6℃ 多云~晴 西北风 1-2级 50 1

In [19]:

# 判断是不是数字
df["yWendu"].str.isnumeric()

Out[19]:

0      False
1      False
2      False
3      False
4      False
5      False
6      False
7      False
8      False
9      False
10     False
11     False
12     False
13     False
14     False
15     False
16     False
17     False
18     False
19     False
20     False
21     False
22     False
23     False
24     False
25     False
26     False
27     False
28     False
29     False
       ...  
335    False
336    False
337    False
338    False
339    False
340    False
341    False
342    False
343    False
344    False
345    False
346    False
347    False
348    False
349    False
350    False
351    False
352    False
353    False
354    False
355    False
356    False
357    False
358    False
359    False
360    False
361    False
362    False
363    False
364    False
Name: yWendu, Length: 365, dtype: bool

In [21]:

# 在数列列上调用str会报错
df["aqi"].str.len()

. . .

## 2. 使用str的startswith, contains等得到的bool的Series可以做条件查询

In [23]:

# 查询三月数据
condition = df["ymd"].str.startswith("2018-03")

In [25]:

condition

. . .

In [27]:

df[condition].head()

Out[27]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
59 2018-03-01 8 -3℃ 多云 西南风 1-2级 46 1
60 2018-03-02 9 -1℃ 晴~多云 北风 1-2级 95 2
61 2018-03-03 13 3℃ 多云~阴 北风 1-2级 214 重度污染 5
62 2018-03-04 7 -2℃ 阴~多云 东南风 1-2级 144 轻度污染 3
63 2018-03-05 8 -3℃ 南风 1-2级 94 2
## 3. 需要多次str处理的链式操作
怎么提取201803这样的数字月份
1. 先将日期2018-03-31替换成20180331的形式
2. 提取月份字符串201803

In [28]:

df["ymd"].str.replace("-","")

. . .

In [29]:

# 每次调用函数,都返回一个新Series
# 不能直接在Series上调用str方法
df["ymd"].str.replace("-","").slice(0, 6)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
 in 
      1 # 每次调用函数,都返回一个新Series
----> 2 df["ymd"].str.replace("-","").slice(0, 6)

D:\Tools\Anaconda3\lib\site-packages\pandas\core\generic.py in __getattr__(self, name)
   5065             if self._info_axis._can_hold_identifiers_and_holds_name(name):
   5066                 return self[name]
-> 5067             return object.__getattribute__(self, name)
   5068 
   5069     def __setattr__(self, name, value):

AttributeError: 'Series' object has no attribute 'slice'

In [31]:

# replace后得到的是Series,通过再次.str后才能切片
df["ymd"].str.replace("-","").str.slice(0, 6)

Out[31]:

0      201801
1      201801
2      201801
3      201801
4      201801
5      201801
6      201801
7      201801
8      201801
9      201801
10     201801
11     201801
12     201801
13     201801
14     201801
15     201801
16     201801
17     201801
18     201801
19     201801
20     201801
21     201801
22     201801
23     201801
24     201801
25     201801
26     201801
27     201801
28     201801
29     201801
        ...  
335    201812
336    201812
337    201812
338    201812
339    201812
340    201812
341    201812
342    201812
343    201812
344    201812
345    201812
346    201812
347    201812
348    201812
349    201812
350    201812
351    201812
352    201812
353    201812
354    201812
355    201812
356    201812
357    201812
358    201812
359    201812
360    201812
361    201812
362    201812
363    201812
364    201812
Name: ymd, Length: 365, dtype: object

In [32]:

# slice就是切片语法,可以直接调用
df["ymd"].str.replace("-","").str[0:6]

Out[32]:

0      201801
1      201801
2      201801
3      201801
4      201801
5      201801
6      201801
7      201801
8      201801
9      201801
10     201801
11     201801
12     201801
13     201801
14     201801
15     201801
16     201801
17     201801
18     201801
19     201801
20     201801
21     201801
22     201801
23     201801
24     201801
25     201801
26     201801
27     201801
28     201801
29     201801
        ...  
335    201812
336    201812
337    201812
338    201812
339    201812
340    201812
341    201812
342    201812
343    201812
344    201812
345    201812
346    201812
347    201812
348    201812
349    201812
350    201812
351    201812
352    201812
353    201812
354    201812
355    201812
356    201812
357    201812
358    201812
359    201812
360    201812
361    201812
362    201812
363    201812
364    201812
Name: ymd, Length: 365, dtype: object

In [37]:

df.head()

Out[37]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3 -6℃ 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2 -5℃ 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2 -5℃ 多云 北风 1-2级 28 1
3 2018-01-04 0 -8℃ 东北风 1-2级 28 1
4 2018-01-05 3 -6℃ 多云~晴 西北风 1-2级 50 1

4. 使用正则表达式的处理

In [42]:

# 添加新列
def get_nianyueri(x):
    year, month, day = x["ymd"].split("-")
    return f"{year}年{month}月{day}日"
df["中文日期"] = df.apply(get_nianyueri, axis=1)

In [40]:

df["中文日期"]

. . .

问题:怎么将"2018年12月31日"中的年,月,日三个中文字符去除

In [44]:

# 方法1:链式replace
df["中文日期"].str.replace("年","").str.replace("月","").str.replace("日","")

. . .

Series.str默认就开启了正则表达式模式

In [43]:

# 方法2:正则表达式替换
df["中文日期"].str.replace("[年月日]","")

. . .

十、Pandas的axis参数怎么理解?

  • axis=0或者"index":
    • 如果是单行操作,就指的是某一行
    • 如果是聚合操作,指的是跨行cross rows
  • axis=1或者"columns":
    • 如果是单列操作,就指的是某一列
    • 如果是聚合操作,指的是跨列cross columns

*按哪个axis,就是这个axis要动起来(类似被for遍历),其它的axis保持不动*

In [ ]:


In [2]:

import pandas as pd
import numpy as np

In [7]:

df = pd.DataFrame(
    np.arange(12).reshape(3,4),
    columns = ["A", "B", "C", "D"]
)

In [8]:

df

Out[8]:

A B C D
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11

1. 单列drop, 就是删除某一列

In [9]:

# 代表的就是删除某列
df.drop("A", axis=1)

Out[9]:

B C D
0 1 2 3
1 5 6 7
2 9 10 11

In [10]:

# 代表的就是删除某行
df.drop(1, axis=0)

Out[10]:

A B C D
0 0 1 2 3
2 8 9 10 11

3. 按照axis=0/index执行mean聚合操作

  • 反直觉:输出的不是每行的结果,而是每列的结果

In [11]:

df

Out[11]:

A B C D
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11

In [16]:

# axis=0 or axis=index
df.mean(axis=0)

Out[16]:

A    4.0
B    5.0
C    6.0
D    7.0
dtype: float64

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2VilxhcD-1597761927700)(http://localhost:8888/notebooks/pandas-learn-code/other_files/pandas-axis-index.png)]

*按哪个axis,就是这个axis要动起来(类似被for遍历),其它的axis保持不动*

3. 按照axis=1/columns执行mean聚合操作

  • 反直觉:输出的不是每行的结果,而是每列的结果

In [21]:

df

Out[21]:

A B C D
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11

In [22]:

# axis=1 or axis = columns
df.mean(axis=1)

Out[22]:

0    1.5
1    5.5
2    9.5
dtype: float64

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XahGzry0-1597761927702)(http://localhost:8888/notebooks/pandas-learn-code/other_files/pandas-axis-columns.png)]

*按哪个axis,就是这个axis要动起来(类似被for遍历),其它的axis保持不动*

5. 再次举例, 加深理解

In [23]:

def get_sum_value(x):
    return x["A"] + x["B"] + x["C"] + x["D"]
df["sum_value"] = df.apply(get_sum_value, axis=1)

In [24]:

df

Out[24]:

A B C D sum_value
0 0 1 2 3 6
1 4 5 6 7 22
2 8 9 10 11 38

*按哪个axis,就是这个axis要动起来(类似被for遍历),其它的axis保持不动*

In [27]:

df["A"]

Out[27]:

0    0
1    4
2    8
Name: A, dtype: int32

十一、Pandas的索引index的用途

把数据存储于普通的column列也能用于数据查询,那使用index有什么好处?

index的用途总结:

  1. 更方便的数据查询;
  2. 使用index可以获得性能提升;
  3. 自动的数据对齐功能;
  4. 更多更强大的数据结构支持;

In [1]:

import pandas as pd

In [2]:

df = pd.read_csv("./pandas-learn-code/datas/ml-latest-small/ratings.csv")

In [3]:

df.head()

Out[3]:

userId movieId rating timestamp
0 1 1 4.0 964982703
1 1 3 4.0 964981247
2 1 6 4.0 964982224
3 1 47 5.0 964983815
4 1 50 5.0 964982931

In [4]:

df.count()

Out[4]:

userId       100836
movieId      100836
rating       100836
timestamp    100836
dtype: int64

1. 使用index查询数据

In [5]:

# drop==False,让索引列还保持在column
# 下列代码实现了将userId设置成了index,同时保留了userId
df.set_index("userId", inplace=True, drop=False)

In [6]:

df.head()

Out[6]:

userId movieId rating timestamp
userId
1 1 1 4.0 964982703
1 1 3 4.0 964981247
1 1 6 4.0 964982224
1 1 47 5.0 964983815
1 1 50 5.0 964982931

In [7]:

df.index

Out[7]:

Int64Index([  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
            ...
            610, 610, 610, 610, 610, 610, 610, 610, 610, 610],
           dtype='int64', name='userId', length=100836)

In [8]:

# 使用index的查询方法:在loc[]中直接写入要查询的参数
# 查询userId为500的用户信息
df.loc[500].head(5)

Out[8]:

userId movieId rating timestamp
userId
500 500 1 4.0 1005527755
500 500 11 1.0 1005528017
500 500 39 1.0 1005527926
500 500 101 1.0 1005527980
500 500 104 4.0 1005528065

In [9]:

# 使用column的condition查询方法
df.loc[df["userId"]==500].head()

Out[9]:

userId movieId rating timestamp
userId
500 500 1 4.0 1005527755
500 500 11 1.0 1005528017
500 500 39 1.0 1005527926
500 500 101 1.0 1005527980
500 500 104 4.0 1005528065

2. 使用index会提升查询性能

  • 如果index是唯一的,Pandas会使用哈希表优化,查询性能为O(1);
  • 如果index不是唯一的,但是有序,Pandas会使用二分查找算法,查询性能为O(logN);
  • 如果index是完全随机的,那么每次查询都要扫描全表,查询性能为O(N);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3xFJcngE-1597761927705)(http://localhost:8888/notebooks/pandas-learn-code/other_files/pandas-index-performance.png)]

实验1:完全随机的查询顺序

In [11]:

# 将数据随机打散
from sklearn.utils import shuffle
df_shuffle = shuffle(df)

In [12]:

df_shuffle.head()

Out[12]:

userId movieId rating timestamp
userId
244 244 1377 4.0 975093513
413 413 3753 5.0 1484439911
280 280 6539 3.5 1348435219
18 18 86332 3.5 1455051197
274 274 3160 2.5 1197275106

In [13]:

# 索引是否是递增的
df_shuffle.index.is_monotonic_increasing

Out[13]:

False

In [14]:

# 索引是否是唯一的
df_shuffle.index.is_unique

Out[14]:

False

In [15]:

# 计时,查看id==500的数据性能
# %timeit将名称执行多次,查看性能
%timeit df_shuffle.loc[500]
366 µs ± 7.09 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

实验2:将index排序后的查询

In [17]:

# 将df_shuffle进行排序
df_sorted = df_shuffle.sort_index()

In [18]:

df_sorted.head()

Out[18]:

userId movieId rating timestamp
userId
1 1 3578 5.0 964980668
1 1 2406 4.0 964982310
1 1 110 4.0 964982176
1 1 2090 5.0 964982838
1 1 2096 4.0 964982838

In [19]:

# 索引是否是递增的
df_sorted.index.is_monotonic_increasing

Out[19]:

True

In [20]:

df_sorted.index.is_unique

Out[20]:

False

In [21]:

%timeit df_sorted.loc[500]
178 µs ± 4.55 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

3.使用index能自动对齐数据

包括Series和DateFrame

In [22]:

s1 = pd.Series([1,2,3], index=list("abc"))

In [23]:

s1

Out[23]:

a    1
b    2
c    3
dtype: int64

In [24]:

s2 = pd.Series([2,3,4], index=list("bcd"))

In [25]:

s2

Out[25]:

b    2
c    3
d    4
dtype: int64

In [26]:

s1 + s2

Out[26]:

a    NaN
b    4.0
c    6.0
d    NaN
dtype: float64

s1,s2都具有b,c索引,而a,d为各自独有,无法对齐,所有相加结果为空

4. 使用index更多更强大的数据结构支持

*很多强大的索引数据结构*

  • CategoricalIndex,基于分类数据的Index,提升性能;
  • MultiIndex,多维索引,用于groupby多维聚合后结果等;
  • DatetimeIndex,时间类型索引,强大的日期和时间的方法支持;

十二、Pandas怎样实现DataFrame的Merge

Pandas的Merge,相当于Sql的Join,将不同的表按key关联到一个表

merge的语法:

pd.merge(left, right, how=‘inner’, on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=True, suffixes=(’*x’, ‘*y’), copy=True, indicator=False, validate=None)

  • left,right:要merge的dataframe或者有name的Series
  • how:join类型,‘left’, ‘right’, ‘outer’, ‘inner’
  • on:join的key,left和right都需要有这个key
  • left_on:left的df或者series的key
  • right_on:right的df或者seires的key
  • left_index,right_index:使用index而不是普通的column做join
  • suffixes:两个元素的后缀,如果列有重名,自动添加后缀,默认是(’*x’, ‘*y’)

文档地址:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.merge.html

本次讲解提纲:

  1. 电影数据集的join实例
  2. 理解merge时一对一、一对多、多对多的数量对齐关系
  3. 理解left join、right join、inner join、outer join的区别
  4. 如果出现非Key的字段重名怎么办

1、电影数据集的join实例

电影评分数据集

是推荐系统研究的很好的数据集
位于本代码目录:./datas/movielens-1m

包含三个文件:

  1. 用户对电影的评分数据 ratings.dat
  2. 用户本身的信息数据 users.dat
  3. 电影本身的数据 movies.dat

可以关联三个表,得到一个完整的大表

数据集官方地址:https://grouplens.org/datasets/movielens/

In [8]:

import pandas as pd

In [12]:

df_ratings = pd.read_csv(
    "./pandas-learn-code/datas/movielens-1m/ratings.dat",
    sep = "::",
    engine = "python",
    names = "UserID::MovieID::Rating::Timestamp".split("::")
)

In [13]:

df_ratings.head()

Out[13]:

UserID MovieID Rating Timestamp
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291

In [14]:

df_users = pd.read_csv(
    "./pandas-learn-code/datas/movielens-1m/users.dat",
    sep = "::",
    engine = "python",
    names = "UserID::Gender::Age::Occupation::Zip-code".split("::")
)

In [15]:

df_users.head()

Out[15]:

UserID Gender Age Occupation Zip-code
0 1 F 1 10 48067
1 2 M 56 16 70072
2 3 M 25 15 55117
3 4 M 45 7 02460
4 5 M 25 20 55455

In [17]:

df_movies = pd.read_csv(
    "./pandas-learn-code/datas/movielens-1m/movies.dat",
    sep = "::",
    engine = "python",
    names = "MovieID::Title::Genres".split("::")
)

In [18]:

df_movies.head()

Out[18]:

MovieID Title Genres
0 1 Toy Story (1995) Animation|Children’s|Comedy
1 2 Jumanji (1995) Adventure|Children’s|Fantasy
2 3 Grumpier Old Men (1995) Comedy|Romance
3 4 Waiting to Exhale (1995) Comedy|Drama
4 5 Father of the Bride Part II (1995) Comedy

In [ ]:

df_

In [21]:

# inner:两边都有某个数据时才会保留
df_ratings_users = pd.merge(
    df_ratings, df_users, left_on="UserID", right_on="UserID", how="inner"
)

In [22]:

df_ratings_users.head()

Out[22]:

UserID MovieID Rating Timestamp Gender Age Occupation Zip-code
0 1 1193 5 978300760 F 1 10 48067
1 1 661 3 978302109 F 1 10 48067
2 1 914 3 978301968 F 1 10 48067
3 1 3408 4 978300275 F 1 10 48067
4 1 2355 5 978824291 F 1 10 48067

In [25]:

df_ratings_users_movies = pd.merge(
    df_ratings_users, df_movies, left_on="MovieID", right_on="MovieID", how="inner"
)

In [26]:

df_ratings_users_movies.head()

Out[26]:

UserID MovieID Rating Timestamp Gender Age Occupation Zip-code Title Genres
0 1 1193 5 978300760 F 1 10 48067 One Flew Over the Cuckoo’s Nest (1975) Drama
1 2 1193 5 978298413 M 56 16 70072 One Flew Over the Cuckoo’s Nest (1975) Drama
2 12 1193 4 978220179 M 25 12 32793 One Flew Over the Cuckoo’s Nest (1975) Drama
3 15 1193 4 978199279 M 25 7 22903 One Flew Over the Cuckoo’s Nest (1975) Drama
4 17 1193 5 978158471 M 50 1 95350 One Flew Over the Cuckoo’s Nest (1975) Drama

2、理解merge时数量的对齐关系

以下关系要正确理解:

  • one-to-one:一对一关系,关联的key都是唯一的
    • 比如(学号,姓名) merge (学号,年龄)
    • 结果条数为:1*1
  • one-to-many:一对多关系,左边唯一key,右边不唯一key
    • 比如(学号,姓名) merge (学号,[语文成绩、数学成绩、英语成绩])
    • 结果条数为:1*N
  • many-to-many:多对多关系,左边右边都不是唯一的
    • 比如(学号,[语文成绩、数学成绩、英语成绩]) merge (学号,[篮球、足球、乒乓球])
    • 结果条数为:M*N

2.1 one-to-one 一对一关系的merge

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gL3djpVk-1597761927707)(http://localhost:8888/notebooks/pandas-learn-code/other_files/pandas-merge-one-to-one.png)]

In [31]:

left = pd.DataFrame({"sno":[11, 12, 13, 14],
                     "name":["name_a","name_b","name_c","name_d"]
                     })
left

Out[31]:

sno name
0 11 name_a
1 12 name_b
2 13 name_c
3 14 name_d

In [28]:

right = pd.DataFrame({"sno":[11, 12, 13, 14],
                     "age":["21","22","23","24"]
                     })
right

Out[28]:

sno age
0 11 21
1 12 22
2 13 23
3 14 24

In [30]:

# 一对一关系,结果中有4条
pd.merge(left, right, on="sno")

Out[30]:

sno name age
0 11 name_a 21
1 12 name_b 22
2 13 name_c 23
3 14 name_d 24

2.2 one-to-many 一对多关系的merge

注意:数据会被复制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7xToCx8V-1597761927707)(http://localhost:8888/notebooks/pandas-learn-code/other_files/pandas-merge-one-to-many.png)]

In [32]:

left = pd.DataFrame({"sno":[11, 12, 13, 14],
                     "name":["name_a","name_b","name_c","name_d"]
                     })
left

Out[32]:

sno name
0 11 name_a
1 12 name_b
2 13 name_c
3 14 name_d

In [33]:

right = pd.DataFrame({"sno":[11, 11, 11, 12, 12, 13],
                     "grade":["语文88","数学90","英语75","语文66", "数学55", "英语29"]
                     })
right

Out[33]:

sno grade
0 11 语文88
1 11 数学90
2 11 英语75
3 12 语文66
4 12 数学55
5 13 英语29

In [35]:

# 数目以多的一边为准
pd.merge(left, right, on="sno")

Out[35]:

sno name grade
0 11 name_a 语文88
1 11 name_a 数学90
2 11 name_a 英语75
3 12 name_b 语文66
4 12 name_b 数学55
5 13 name_c 英语29

2.3 many-to-many 多对多关系的merge

注意:结果数量会出现乘法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y9grLyta-1597761927708)(http://localhost:8888/notebooks/pandas-learn-code/other_files/pandas-merge-many-to-many.png)]

In [36]:

left = pd.DataFrame({"sno":[11, 11, 12, 12, 12],
                     "爱好":["篮球","羽毛球","乒乓球","篮球", "足球"]
                     })
left

Out[36]:

sno 爱好
0 11 篮球
1 11 羽毛球
2 12 乒乓球
3 12 篮球
4 12 足球

In [37]:

right = pd.DataFrame({"sno":[11, 11, 11, 12, 12, 13],
                     "grade":["语文88","数学90","英语75","语文66", "数学55", "英语29"]
                     })
right

Out[37]:

sno grade
0 11 语文88
1 11 数学90
2 11 英语75
3 12 语文66
4 12 数学55
5 13 英语29

In [38]:

pd.merge(left, right, on="sno")

Out[38]:

sno 爱好 grade
0 11 篮球 语文88
1 11 篮球 数学90
2 11 篮球 英语75
3 11 羽毛球 语文88
4 11 羽毛球 数学90
5 11 羽毛球 英语75
6 12 乒乓球 语文66
7 12 乒乓球 数学55
8 12 篮球 语文66
9 12 篮球 数学55
10 12 足球 语文66
11 12 足球 数学55

3、理解left join、right join、inner join、outer join的区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-45nVqPR9-1597761927708)(http://localhost:8888/notebooks/pandas-learn-code/other_files/pandas-leftjoin-rightjoin-outerjoin.png)]

In [52]:

left = pd.DataFrame({"key":["K0", "K1", "K2", "K3"],
                      "A":["A0","A1","A2","A3"],
                      "B":["B0","B1","B2","B3"]})

right = pd.DataFrame({"key":["K0", "K1", "K4", "K5"],
                       "C":["C0","C1","C2","C3"],
                       "D":["D0","D1","D2","D3"]})

In [53]:

left

Out[53]:

key A B
0 K0 A0 B0
1 K1 A1 B1
2 K2 A2 B2
3 K3 A3 B3

In [54]:

right

Out[54]:

key C D
0 K0 C0 D0
1 K1 C1 D1
2 K4 C2 D2
3 K5 C3 D3

3.1 inner join,默认

左边和右边的key都有,才会出现在结果里

In [55]:

pd.merge(left, right, how="inner")

Out[55]:

key A B C D
0 K0 A0 B0 C0 D0
1 K1 A1 B1 C1 D1

3.2 left join

左边的都会出现在结果里,右边的如果无法匹配则为Null

In [56]:

pd.merge(left, right, how="left")

Out[56]:

key A B C D
0 K0 A0 B0 C0 D0
1 K1 A1 B1 C1 D1
2 K2 A2 B2 NaN NaN
3 K3 A3 B3 NaN NaN

3.3 right join

右边的都会出现在结果里,左边的如果无法匹配则为Null

In [57]:

pd.merge(left, right, how="right")

Out[57]:

key A B C D
0 K0 A0 B0 C0 D0
1 K1 A1 B1 C1 D1
2 K4 NaN NaN C2 D2
3 K5 NaN NaN C3 D3

3.4 outer join

左边、右边的都会出现在结果里,如果无法匹配则为Null

In [58]:

pd.merge(left, right, how="outer")

Out[58]:

key A B C D
0 K0 A0 B0 C0 D0
1 K1 A1 B1 C1 D1
2 K2 A2 B2 NaN NaN
3 K3 A3 B3 NaN NaN
4 K4 NaN NaN C2 D2
5 K5 NaN NaN C3 D3

4、如果出现非Key的字段重名怎么办

In [61]:

left = pd.DataFrame({"key":["K0", "K1", "K2", "K3"],
                      "A":["A0","A1","A2","A3"],
                      "B":["B0","B1","B2","B3"]})

right = pd.DataFrame({"key":["K0", "K1", "K4", "K5"],
                       "A":["A10","A11","A12","A13"],
                       "D":["D0","D1","D4","D5"]})

In [60]:

left

Out[60]:

key A B
0 K0 A0 B0
1 K1 A1 B1
2 K2 A2 B2
3 K3 A3 B3

In [62]:

right

Out[62]:

key A D
0 K0 A10 D0
1 K1 A11 D1
2 K4 A12 D4
3 K5 A13 D5

In [64]:

pd.merge(left, right, on="key")

Out[64]:

key A_x B A_y D
0 K0 A0 B0 A10 D0
1 K1 A1 B1 A11 D1

In [65]:

# 两个元素的后缀,如果列有重名,自动添加后缀,默认是('x', 'y')
pd.merge(left, right, on="key", suffixes=('_left', '_right'))

Out[65]:

key A_left B A_right D
0 K0 A0 B0 A10 D0
1 K1 A1 B1 A11 D1

十三、Pandas实现数据的合并concat

使用场景:

批量合并相同格式的Excel、给DataFrame添加行、给DataFrame添加列

一句话说明concat语法:

  • 使用某种合并方式(inner/outer)
  • 沿着某个轴向(axis=0/1)
  • 把多个Pandas对象(DataFrame/Series)合并成一个。

concat语法:pandas.concat(objs, axis=0, join=‘outer’, ignore_index=False)

  • objs:一个列表,内容可以是DataFrame或者Series,可以混合
  • axis:默认是0代表按行合并,如果等于1代表按列合并
  • join:合并的时候索引的对齐方式,默认是outer join,也可以是inner join
  • ignore_index:是否忽略掉原来的数据索引

append语法:DataFrame.append(other, ignore_index=False)

append只有按行合并,没有按列合并,相当于concat按行的简写形式

  • other:单个dataframe、series、dict,或者列表
  • ignore_index:是否忽略掉原来的数据索引

参考文档:

  • pandas.concat的api文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.concat.html
  • pandas.concat的教程:https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html
  • pandas.append的api文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.append.html

In [1]:

import pandas as pd

import warnings
warnings.filterwarnings("ignore")

一、使用Pandas.concat合并数据

In [2]:

df1 = pd.DataFrame({"A":["A0","A1","A2","A3"],
                    "B":["B0","B1","B2","B3"],
                    "C":["C0","C1","C2","C3"],
                    "D":["D0","D1","D2","D3"],
                    "E":["E0","E1","E2","E3"]
                   })
df1

Out[2]:

A B C D E
0 A0 B0 C0 D0 E0
1 A1 B1 C1 D1 E1
2 A2 B2 C2 D2 E2
3 A3 B3 C3 D3 E3

In [3]:

df2 = pd.DataFrame({"A":["A4","A5","A6","A7"],
                    "B":["B4","B5","B6","B7"],
                    "C":["C4","C5","C6","C7"],
                    "D":["D4","D5","D6","D7"],
                    "F":["F4","F5","F6","F7"]
                   })
df2

Out[3]:

A B C D F
0 A4 B4 C4 D4 F4
1 A5 B5 C5 D5 F5
2 A6 B6 C6 D6 F6
3 A7 B7 C7 D7 F7

1. 默认的concat, 参数为axis=0, join=outer, ignore_index=False

In [4]:

pd.concat([df1, df2])

Out[4]:

A B C D E F
0 A0 B0 C0 D0 E0 NaN
1 A1 B1 C1 D1 E1 NaN
2 A2 B2 C2 D2 E2 NaN
3 A3 B3 C3 D3 E3 NaN
0 A4 B4 C4 D4 NaN F4
1 A5 B5 C5 D5 NaN F5
2 A6 B6 C6 D6 NaN F6
3 A7 B7 C7 D7 NaN F7

2. 使用ignore_index=True可以忽略原来的索引

In [5]:

pd.concat([df1, df2], ignore_index=True)

Out[5]:

A B C D E F
0 A0 B0 C0 D0 E0 NaN
1 A1 B1 C1 D1 E1 NaN
2 A2 B2 C2 D2 E2 NaN
3 A3 B3 C3 D3 E3 NaN
4 A4 B4 C4 D4 NaN F4
5 A5 B5 C5 D5 NaN F5
6 A6 B6 C6 D6 NaN F6
7 A7 B7 C7 D7 NaN F7

3. 使用join=inner过滤掉不匹配的列

In [6]:

pd.concat([df1, df2], ignore_index=True, join="inner")

Out[6]:

A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
3 A3 B3 C3 D3
4 A4 B4 C4 D4
5 A5 B5 C5 D5
6 A6 B6 C6 D6
7 A7 B7 C7 D7

4. 使用axis=1相当于添加新列

In [7]:

df1

Out[7]:

A B C D E
0 A0 B0 C0 D0 E0
1 A1 B1 C1 D1 E1
2 A2 B2 C2 D2 E2
3 A3 B3 C3 D3 E3

A:添加一列Series

In [9]:

s1 = pd.Series(list(range(4)), name="F")
pd.concat([df1, s1], axis=1)

Out[9]:

A B C D E F
0 A0 B0 C0 D0 E0 0
1 A1 B1 C1 D1 E1 1
2 A2 B2 C2 D2 E2 2
3 A3 B3 C3 D3 E3 3

B:添加多列Series

In [10]:

s2 = df1.apply(lambda x:x["A"] + "_GG", axis=1)

In [11]:

s2

Out[11]:

0    A0_GG
1    A1_GG
2    A2_GG
3    A3_GG
dtype: object

In [12]:

s2.name="G"

In [13]:

# 列表可以只有Series
pd.concat([s1,s2], axis=1)

Out[13]:

F G
0 0 A0_GG
1 1 A1_GG
2 2 A2_GG
3 3 A3_GG

In [14]:

# 列表是可以混合顺序的
pd.concat([s1, df1, s2], axis=1)

Out[14]:

F A B C D E G
0 0 A0 B0 C0 D0 E0 A0_GG
1 1 A1 B1 C1 D1 E1 A1_GG
2 2 A2 B2 C2 D2 E2 A2_GG
3 3 A3 B3 C3 D3 E3 A3_GG

二、使用DateFrame.append按行合并数据

In [15]:

df1 = pd.DataFrame([[1, 2], [3, 4]], columns=list("AB"))
df1

Out[15]:

A B
0 1 2
1 3 4

In [16]:

df2 = pd.DataFrame([[5, 6], [7, 8]], columns=list("AB"))
df2

Out[16]:

A B
0 5 6
1 7 8

1. 给一个DataFrame添加另一个DataFrame

In [18]:

df1.append(df2)

Out[18]:

A B
0 1 2
1 3 4
0 5 6
1 7 8

2. 忽略原来的索引,另ignore_index=True

In [19]:

df1.append(df2, ignore_index=True)

Out[19]:

A B
0 1 2
1 3 4
2 5 6
3 7 8

3.可以一行一行的给DataFrame添加数据

In [21]:

# 创建一个空的df
df = pd.DataFrame(columns=["A"])
df

Out[21]:

A

A:低性能版

In [22]:

for i in range(5):
    # 注意:这里每次都在复制    
    df = df.append({"a":i}, ignore_index=True)
df

Out[22]:

A a
0 NaN 0.0
1 NaN 1.0
2 NaN 2.0
3 NaN 3.0
4 NaN 4.0

B:性能好的版本

In [23]:

# 第一个
pd.concat(
    [pd.DataFrame([i], columns=["A"]) for i in range(5)],
    ignore_index=True
)

Out[23]:

A
0 0
1 1
2 2
3 3
4 4

In [27]:

ss = pd.DataFrame( i for i in range(5))
ss

Out[27]:

0
0 0
1 1
2 2
3 3
4 4

十四、Pandas批量拆分Excel与合并Excel

实例演示:

  1. 将一个大Excel等份拆成多个Excel
  2. 将多个小Excel合并成一个大Excel并标记来源

In [51]:

work_dir = "D:/WinterIsComing/python/New_Wave/pandas_basic/15.excel_split_merge"
# 用来放置拆分后的小文件
splits_dir = f"{work_dir}/splits"

In [52]:

import os 
if not os.path.exists(splits_dir):
    os.mkdir(splits_dir)

0. 读取源Excel到Pandas

In [54]:

import pandas as pd

In [53]:

df_source = pd.read_excel(r"D:/WinterIsComing/python/New_Wave/pandas_basic/15.excel_split_merge/crazyant_blog_articles_source.xlsx")

In [55]:

df_source.head()

Out[55]:

id title tags
0 2585 Tensorflow怎样接收变长列表特征 python,tensorflow,特征工程
1 2583 Pandas实现数据的合并concat pandas,python,数据分析
2 2574 Pandas的Index索引有什么用途? pandas,python,数据分析
3 2564 机器学习常用数据集大全 python,机器学习
4 2561 一个数据科学家的修炼路径 数据分析

In [56]:

df_source.index

Out[56]:

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

In [57]:

# 258行,3列
df_source.shape

Out[57]:

(258, 3)

In [58]:

# 通过df_source.shape得到元组(258, 3)
# 通过df_source.shape[0]得到行数
total_row_count = df_source.shape[0]
total_row_count

Out[58]:

258

1、将一个大excel等份拆成多个Excel

  1. 使用df.iloc方法,将一个大的DataFrame,拆分成多个小DataFrame
  2. 将使用DataFrame.to_excel保存每个小Excel

1. 1 计算拆分后的每个excel的行数

In [59]:

# 将一个大Excel,拆分给这几个人
user_names = ["A", "B", "C", "D", "E", "F"]

In [60]:

# 每个人的任务数目
split_size = total_row_count // len(user_names)
# 此处的作用在于如果有余数,可以将未分配的行数,分配给前面几人,保证所有的行都分配出去
if total_row_count % len(user_names) != 0:
    split_size += 1
    
split_size    

Out[60]:

43

1.2 拆分成多个DataFrame

In [64]:

df_subs = []
for idx, user_name in enumerate(user_names):
    # iloc的开始索引
    begin = idx*split_size
    # iloc的结束索引
    end = begin + split_size
    # 实现df按照iloc拆分
    df_sub = df_source.iloc[begin:end]
    # 将每个子df存入列表
    df_subs.append((idx, user_name, df_sub))

df_subs[0][2].head(5)

Out[64]:

id title tags
0 2585 Tensorflow怎样接收变长列表特征 python,tensorflow,特征工程
1 2583 Pandas实现数据的合并concat pandas,python,数据分析
2 2574 Pandas的Index索引有什么用途? pandas,python,数据分析
3 2564 机器学习常用数据集大全 python,机器学习
4 2561 一个数据科学家的修炼路径 数据分析

In [65]:

df_subs[1][2].head(5)

Out[65]:

id title tags
43 2120 Zookeeper并不保证读取的是最新数据 zookeeper
44 2089 Mybatis源码解读-初始化过程详解 mybatis
45 2076 怎样借助Python爬虫给宝宝起个好名字 python,爬虫
46 2022 Mybatis源码解读-设计模式总结 mybatis,设计模式
47 2012 打工者心态、主人公意识、个人公司品牌 程序人生

1.3 将每个DataFrame存入excel

In [63]:

for idx, user_name, df_sub in df_subs:
    file_name = f"{splits_dir}/spike_pandas_{idx}_{user_name}.xlsx"
    df_sub.to_excel(file_name, index=False)

2、合并多个小Excel到一个大Excel

  1. 遍历文件夹,得到要合并的Excel文件列表
  2. 分别读取到DataFrame,给每个df添加一列用于标记来源
  3. 使用pd.concat进行df批量合并
  4. 将合并后的DataFrame输出到excel

2.1 遍历文件夹,得到要合并的Excel名称列表

In [66]:

import os
excel_names = []
# listdir返回指定目录下的所有文件和文件夹名称
for excel_name in os.listdir(splits_dir):
    excel_names.append(excel_name)
excel_names

Out[66]:

['spike_pandas_0_A.xlsx',
 'spike_pandas_1_B.xlsx',
 'spike_pandas_2_C.xlsx',
 'spike_pandas_3_D.xlsx',
 'spike_pandas_4_E.xlsx',
 'spike_pandas_5_F.xlsx']

2.2 分别读取到DataFrame

In [70]:

df_list = []

for excel_name in excel_names:
    # 读取每个excel到df
    excel_path = f"{splits_dir}/{excel_name}"
    df_split = pd.read_excel(excel_path)
    # 得到username,通过字符串切片
    username = excel_name.replace("spike_pandas_", "").replace(".xlsx", "")[2:]
    # print(username)
    # 给df_split添加一列username
    df_split["username"] = username
    
    df_list.append(df_split)

2.3 使用pd.concat进行合并

In [71]:

df_merged = pd.concat(df_list)

In [72]:

df_merged.shape

Out[72]:

(258, 4)

In [74]:

df_merged.head()

Out[74]:

id title tags username
0 2585 Tensorflow怎样接收变长列表特征 python,tensorflow,特征工程 A
1 2583 Pandas实现数据的合并concat pandas,python,数据分析 A
2 2574 Pandas的Index索引有什么用途? pandas,python,数据分析 A
3 2564 机器学习常用数据集大全 python,机器学习 A
4 2561 一个数据科学家的修炼路径 数据分析 A

In [76]:

df_merged["username"].value_counts()

Out[76]:

B    43
F    43
D    43
E    43
A    43
C    43
Name: username, dtype: int64

2.4 将合并后的DataFrame输出到Excel

In [77]:

df_merged.to_excel(f"{work_dir}/spike_pandas_merged.xlsx", index=False)

十五、Pandas怎样实现groupby分组统计

类似SQL:
select city,max(temperature) from city_weather group by city;

groupby:先对数据分组,然后在每个分组上应用聚合函数、转换函数

本次演示:
一、分组使用聚合函数做数据统计
二、遍历groupby的结果理解执行流程
三、实例分组探索天气数据

In [1]:

import pandas as pd
import numpy as np
# 加上这一句,能在jupyter notebook展示matplo图表
%matplotlib inline

In [4]:

df = pd.DataFrame({'A': ['foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'foo'],
                   'B': ['one', 'one', 'two', 'three', 'two', 'two', 'one', 'three'],
                   'C': np.random.randn(8),
                   'D': np.random.randn(8)})
df

Out[4]:

A B C D
0 foo one -0.102369 0.042233
1 bar one 1.552845 -0.623522
2 foo two 0.770077 0.205682
3 bar three -1.989910 -0.617111
4 foo two 1.230455 -0.422428
5 bar two -0.697516 -0.964579
6 foo one -0.939646 -0.414017
7 foo three 0.763570 0.451086

1、分组使用聚合函数做数据统计

1.1 单个列groupby,查询所有数据列的统计

In [5]:

df.groupby("A").sum()

Out[5]:

C D
A
bar -1.134582 -2.205211
foo 1.722086 -0.137444

我们看到:

  1. groupby中的’A’变成了数据的索引列
  2. 因为要统计sum,但B列不是数字,所以被自动忽略掉

1.2 多个列groupby,查询所有数据列的统计

In [6]:

# 以A,B为索引,查询C,D的平均值
df.groupby(["A", "B"]).mean()

Out[6]:

C D
A B
bar one 1.552845 -0.623522
three -1.989910 -0.617111
two -0.697516 -0.964579
foo one -0.521008 -0.185892
three 0.763570 0.451086
two 1.000266 -0.108373

In [7]:

# 取消A.B作为索引
df.groupby(["A", "B"], as_index=False).mean()

Out[7]:

A B C D
0 bar one 1.552845 -0.623522
1 bar three -1.989910 -0.617111
2 bar two -0.697516 -0.964579
3 foo one -0.521008 -0.185892
4 foo three 0.763570 0.451086
5 foo two 1.000266 -0.108373

1.3 同时查看多种数据统计

In [8]:

df.groupby("A").agg([np.sum, np.mean, np.std])

Out[8]:

C D
sum mean std sum mean std
A
bar -1.134582 -0.378194 1.792834 -2.205211 -0.735070 0.198786
foo 1.722086 0.344417 0.864635 -0.137444 -0.027489 0.385242

我们看到:列变成了多级索引

1.4 查看单列的结果数据统计

In [10]:

# 预过滤,性能更好
df.groupby("A")["C"].agg([np.sum, np.mean, np.std])

Out[10]:

sum mean std
A
bar -1.134582 -0.378194 1.792834
foo 1.722086 0.344417 0.864635

In [9]:

# 方法2
df.groupby("A").agg([np.sum, np.mean, np.std])["C"]

Out[9]:

sum mean std
A
bar -1.134582 -0.378194 1.792834
foo 1.722086 0.344417 0.864635

1.5 不同列使用不同的聚合函数

In [12]:

# 以字典的形式对不同的列使用不同的聚合函数
df.groupby("A").agg({"C":np.sum, "D":np.mean})

Out[12]:

C D
A
bar -1.134582 -0.735070
foo 1.722086 -0.027489

2、遍历groupby的结果理解执行流程

for循环可以直接遍历每个group

2.1 遍历单个列聚合的分组

In [13]:

g = df.groupby("A")
g

Out[13]:


In [16]:

df

Out[16]:

A B C D
0 foo one -0.102369 0.042233
1 bar one 1.552845 -0.623522
2 foo two 0.770077 0.205682
3 bar three -1.989910 -0.617111
4 foo two 1.230455 -0.422428
5 bar two -0.697516 -0.964579
6 foo one -0.939646 -0.414017
7 foo three 0.763570 0.451086

In [15]:

for name,group in g:
    print(name)
    print(group)
    print()
# name:bar and foo
# group:是两个DataFrame
bar
     A      B         C         D
1  bar    one  1.552845 -0.623522
3  bar  three -1.989910 -0.617111
5  bar    two -0.697516 -0.964579

foo
     A      B         C         D
0  foo    one -0.102369  0.042233
2  foo    two  0.770077  0.205682
4  foo    two  1.230455 -0.422428
6  foo    one -0.939646 -0.414017
7  foo  three  0.763570  0.451086

*可以获取单个分组的数据*

In [17]:

g.get_group("bar")

Out[17]:

A B C D
1 bar one 1.552845 -0.623522
3 bar three -1.989910 -0.617111
5 bar two -0.697516 -0.964579

2.2 遍历多个列聚合的分组

In [20]:

g = df.groupby(["A", "B"])
g

Out[20]:


In [21]:

for name, group in g:
    print(name)
    print(group)
    print()
# 分组的名称变成了元组
('bar', 'one')
     A    B         C         D
1  bar  one  1.552845 -0.623522

('bar', 'three')
     A      B        C         D
3  bar  three -1.98991 -0.617111

('bar', 'two')
     A    B         C         D
5  bar  two -0.697516 -0.964579

('foo', 'one')
     A    B         C         D
0  foo  one -0.102369  0.042233
6  foo  one -0.939646 -0.414017

('foo', 'three')
     A      B        C         D
7  foo  three  0.76357  0.451086

('foo', 'two')
     A    B         C         D
2  foo  two  0.770077  0.205682
4  foo  two  1.230455 -0.422428

可以看到,name是一个2个元素的tuple,代表不同的列

In [22]:

g.get_group(("foo", "one"))

Out[22]:

A B C D
0 foo one -0.102369 0.042233
6 foo one -0.939646 -0.414017

*可以直接查询group后的某几列,生成Series或者子DataFrame*

In [24]:

# 获得一个SeriesGroupBy
g["C"]

Out[24]:


In [25]:

for name, group in g["C"]:
    print(name)
    print(group)
    print(type(group))
    print()
('bar', 'one')
1    1.552845
Name: C, dtype: float64


('bar', 'three')
3   -1.98991
Name: C, dtype: float64


('bar', 'two')
5   -0.697516
Name: C, dtype: float64


('foo', 'one')
0   -0.102369
6   -0.939646
Name: C, dtype: float64


('foo', 'three')
7    0.76357
Name: C, dtype: float64


('foo', 'two')
2    0.770077
4    1.230455
Name: C, dtype: float64

其实所有的聚合统计,都是在dataframe和series上进行的;

3、实例分组探索天气数据

In [27]:

fpath = "./pandas-learn-code/datas/beijing_tianqi/beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)
df.head()

Out[27]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3℃ -6℃ 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2℃ -5℃ 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2℃ -5℃ 多云 北风 1-2级 28 1
3 2018-01-04 0℃ -8℃ 东北风 1-2级 28 1
4 2018-01-05 3℃ -6℃ 多云~晴 西北风 1-2级 50 1

In [28]:

# 替换掉温度的后缀℃
df.loc[:, "bWendu"] = df["bWendu"].str.replace("℃","").astype("int32")
df.loc[:, "yWendu"] = df["yWendu"].str.replace("℃","").astype("int32")
df.head()

Out[28]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3 -6 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2 -5 多云 北风 1-2级 28 1
3 2018-01-04 0 -8 东北风 1-2级 28 1
4 2018-01-05 3 -6 多云~晴 西北风 1-2级 50 1

In [29]:

# 新增一列为月份
df["month"] = df["ymd"].str[:7]
df.head()

Out[29]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel month
0 2018-01-01 3 -6 晴~多云 东北风 1-2级 59 2 2018-01
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1 2018-01
2 2018-01-03 2 -5 多云 北风 1-2级 28 1 2018-01
3 2018-01-04 0 -8 东北风 1-2级 28 1 2018-01
4 2018-01-05 3 -6 多云~晴 西北风 1-2级 50 1 2018-01

3.1 查看每个月的最高温度

In [31]:

data = df.groupby("month")["bWendu"].max()
data

Out[31]:

month
2018-01     7
2018-02    12
2018-03    27
2018-04    30
2018-05    35
2018-06    38
2018-07    37
2018-08    36
2018-09    31
2018-10    25
2018-11    18
2018-12    10
Name: bWendu, dtype: int32

In [32]:

type(data)

Out[32]:

pandas.core.series.Series

In [34]:

data.plot()

Out[34]:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ssxk6ssS-1597761927710)(data:image/png;base64,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)]

3.2 查看每个月的最高温度、最低温度、平均空气质量指数

In [35]:

df.head()

Out[35]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel month
0 2018-01-01 3 -6 晴~多云 东北风 1-2级 59 2 2018-01
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1 2018-01
2 2018-01-03 2 -5 多云 北风 1-2级 28 1 2018-01
3 2018-01-04 0 -8 东北风 1-2级 28 1 2018-01
4 2018-01-05 3 -6 多云~晴 西北风 1-2级 50 1 2018-01

In [38]:

group_data = df.groupby("month").agg({"bWendu":np.max, "yWendu":np.min, "aqi":np.mean})
group_data

Out[38]:

bWendu yWendu aqi
month
2018-01 7 -12 60.677419
2018-02 12 -10 78.857143
2018-03 27 -4 130.322581
2018-04 30 1 102.866667
2018-05 35 10 99.064516
2018-06 38 17 82.300000
2018-07 37 22 72.677419
2018-08 36 20 59.516129
2018-09 31 11 50.433333
2018-10 25 1 67.096774
2018-11 18 -4 105.100000
2018-12 10 -12 77.354839

In [39]:

group_data.plot()

Out[39]:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lwmi8hwM-1597761927711)(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAXoAAAEKCAYAAAAcgp5RAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzs3Xd4VFX6wPHvSe+kE0hIAiQQOoTQi6AiSLEg1XVX176ua0Fsuz/B3VVXUFlZXVxx7UhHUVDRtQSQTugkgYSSkFASUgjpycz5/XGHEKSFtJlM3s/z5GHmzp173wmT95577rnvUVprhBBC2C8HawcghBCiYUmiF0IIOyeJXggh7JwkeiGEsHOS6IUQws5JohdCCDsniV4IIeycJHohhLBzkuiFEMLOOVk7AIDAwEAdGRlp7TCEEKJJSUhIOK21DrraejaR6CMjI9m+fbu1wxBCiCZFKZVWk/Wk60YIIeycJHohhLBzkuiFEMLO2UQfvRAVFRVkZGRQWlpq7VCaBDc3N8LCwnB2drZ2KKIJkEQvbEJGRgbe3t5ERkailLJ2ODZNa01OTg4ZGRm0bdvW2uGIJkC6boRNKC0tJSAgQJJ8DSilCAgIkLMfUWOS6IXNkCRfc/K7EtdCEr0VHMg9wJYTW6wdhhCimZBE38jM2sxTa5/iof89xNYTW60djqjm6NGjdO3a9YJlX375JbfddlvV83/84x9ERUVVPV+1ahW33HJLvew/MjKS06dP18u2hKhOEn0j23h8I2kFabg5uTF97XSOFx63dkjiCgYOHMimTZuqnm/atAkfHx+ysrIA2LhxI4MGDbJWeELUiCT6RrYwaSGB7oF8evOnVJgreOLnJyitlItqtqKyspK7776b7t27M2HCBDw9PWnRogWpqakAZGZmcscdd7Bx40bASPQDBw4E4Pvvv2fAgAHExsYyceJECgsLAaOlPnPmTGJjY+nWrRvJyckA5OTkcNNNN9GrVy8eeughtNbAxWcWr7/+Oi+++GJj/QqEHZLhlY0ovSCdXzJ/4eEeDxPtF80/hvyDP/30J/6++e+8NOglucBm8ddV+0k8XlCv2+zc2oeZ47pcdb0DBw7w/vvvM2jQIO69917mzZvHwIED2bhxIyaTiejoaPr37893333H2LFj2bNnD3369OH06dO89NJL/PDDD3h6ejJr1izmzJnDjBkzAAgMDGTHjh3MmzeP119/nf/+97/89a9/ZfDgwcyYMYOvv/6a+fPn1+tnFuIcadE3okXJi3BUjkzsMBGAYW2G8UiPR/jq0FcsTF5o5egEQJs2baq6Yu666y5++eUXBg0axMaNG9m4cSMDBgygb9++bNmyhZ07d9KxY0fc3NzYvHkziYmJDBo0iJ49e/Lxxx+Tlna+3tT48eMB6N27N0ePHgVg3bp13HXXXQCMGTMGPz+/xv2wotmQFn0jKa4oZmXqSkZEjiDI43xV0Yd6PERibiKvbXuNDn4d6BPSx4pR2oaatLwbyq/PqpRSDBw4kLfeeguTycQDDzyAt7c3paWlxMfHVx0UtNaMGDGCRYsWXXK7rq6uADg6OlJZWXnZ/QE4OTlhNpurnst4eVFX0qJvJKsOraKwopA7Y+68YLmDcuCVwa/QxrsN09dO52TRSStFKADS09OrLr4uWrSIwYMH07lzZ44fP8769evp1asXAD179uQ///lPVf98//792bBhQ1VffnFxMQcPHrzivoYOHcpnn30GwLfffkteXh4ALVu2JCsri5ycHMrKyli9enWDfFbRfEiibwRaaxYlL6JzQGd6BPW46HVvF2/mXj+XMlOZXJy1sk6dOvHxxx/TvXt3cnNz+cMf/oBSin79+hEYGFhVW2bAgAEcPny4KtEHBQXx0UcfMXXqVLp3707//v2rLrpezsyZM1m3bh2xsbF8//33hIeHA+Ds7MyMGTPo168fY8eOJSYmpmE/tLB76tyVfmuKi4vT9jzxyOYTm3ng+wd4adBL3Bp162XX+yn9Jx7/+XFuaX9Ls7s4m5SURKdOnawdRpMivzOhlErQWsddbT1p0TeChUkL8XP1Y1TbUVdc7/rw63m4x8N8degrFh9Y3EjRCSHsnST6BpZZmMnajLVM6DABV0fXq67/hx5/4Lqw65i9dTbbT9rvWY4QovFcNdErpT5QSmUppfZVW/aaUipZKbVHKfWFUsq32mvPK6VSlVIHlFIjGyrwpmJJ8hIUikkdJ9VofQflwD+G/IMw7zCeWvuUXJwVQtRZTVr0HwG/7nP4H9BVa90dOAg8D6CU6gxMAbpY3jNPKeVYb9E2MSWVJaxIWcH14dcT4hlS4/d5u3jz5vA3Ka0sZVr8NMpMZQ0YpRDC3l010Wut1wG5v1r2vdb63GDgzUCY5fGtwGKtdZnW+giQCvStx3iblG8Of0NBecFFQypror1ve14Z/Ap7T+/l5c0vYwsXzYUQTVN99NHfC3xreRwKHKv2WoZlWbOjtWZh8kI6+HWgd8vetdrGDRE38GD3B/ki9QuWHlhazxEKIZqLOiV6pdRfgErgs3OLLrHaJZuiSqkHlVLblVLbs7Oz6xKGTUo4lcDBvIPcGXNnnYZJPtLjEYaEDuHVra+y49SOeoxQXM3tt9/OypUrq5537NiRl156qer5HXfcweeff17n/cTHxzN27Ng6b0eIy6l1oldK3Q2MBX6jz/crZABtqq0WBlyyDq/Wer7WOk5rHRcUFHSpVZq0hckL8XHxYXS70XXajqODI68OfZXWXq2ZFj+NU0Wn6ilCcTXnipmBUWnSy8vropLF526YEsKW1SrRK6VGAc8Ct2iti6u99BUwRSnlqpRqC0QDzW52jZNFJ/kp/SfuiL4Ddyf3Om/Px8WHucPnUlxZzLT4aZSbyushSlHdCy+8wNy5c6ue/+Uvf6GkpOSCcsRjx44lOzsbrTVHjhzB3d2dkJAQTCYTTz/9NH369KF79+68++67gNFSHzZsGBMmTCAmJobf/OY3Vdda1qxZQ0xMDIMHD77grODFF1/k9ddfr3retWvXqiJoQtTWVYuaKaUWAcOAQKVUBjATY5SNK/A/S7fEZq31w1rr/UqppUAiRpfOH7XWpoYK3lYtPbAUjWZyzOR622aUXxQvD36ZafHTeGXLK7w48MV627bN+fY5OLm3frcZ0g1ufvWyL993332MHz+exx9/HLPZzOLFi9m4cSNz5syhvLycjRs3ct1113H48GGSkpLYuXNnVUGz999/nxYtWrBt2zbKysoYNGgQN910EwA7d+5k//79tG7dmkGDBrFhwwbi4uJ44IEH+Omnn4iKimLy5Pr7nghxKVdN9FrrqZdY/P4V1n8ZeLkuQTVlZaYylh9cznVh1xHqVb/XoUdEjOCBbg/w3t736BLYparcsai7yMhIAgIC2LlzJ6dOnaJXr160bNmSLl26sGPHDjZv3swzzzzD4cOH2bhxIzt37rxgwpE9e/awfPlyAM6cOUNKSgouLi707duXsDBjUFrPnj05evQoXl5etG3blujoaMAohyy16EVDkjLF9WzNkTXkleVxZ6drH1JZE3/s+UcScxN5ZcsrRPtG0zO4Z4Psx6qu0PJuSPfffz8fffQRJ0+e5N577wWMfvp169Zx9uxZ/Pz86N+/P2+//TY7d+7k4YcfBowRVm+99RYjR154f2B8fHxVeWK4sETx5S7QS4li0RCkBEI9Ojeksn2L9vQL6dcg+3B0cGTWkFm08mzFk/FPklWc1SD7aY5uv/121qxZw7Zt26qS9qBBg3j33Xfp0cOoOtq9e3c2b95Meno6XboYdfNHjhzJO++8Q0VFBQAHDx6kqKjosvuJiYnhyJEjHDp0COCCGvaRkZHs2GGMrtqxYwdHjhyp/w8qmh1J9PVod/ZuEnMSmRoztUErT7ZwbcHc4XMpqihiWvw0KkwVDbav5sTFxYXhw4czadIkHB2NG7oHDhzI4cOHGTBgAGC0uIODg4mLi8PBwfjzuf/+++ncuTOxsbF07dqVhx566ILJRX7Nzc2N+fPnM2bMGAYPHkxERETVa3fccQe5ubn07NmTd955hw4dOjTgJxbNhZQprkfPrHuGXzJ+4YeJP+Dh7NHg+/vu6HdMXzudiR0mMmPAjAbfX0OyhZK7ZrOZ2NhYli1bVtV/bsts4XcmrEvKFDey7OJs/nf0f9wadWujJHmAkZEjubfrvSw7uIzlB5c3yj7tVWJiIlFRUdxwww1NIskLcS3kYmw9WXZwGSZtYmrMpQYpNZzHej1Gcm6ycXHWL/qSM1iJq+vcuTOHDx+2dhhCNAhp0deDClMFyw4uY3DoYMJ9wht1344OjsweOptgj2Cm/TyN0yWnG3X/QgjbJ4m+Hnyf9j2nS0432JDKqzl3cfZsxVm5OCuEuIgk+nqwMHkhET4RDGxtvbonHf078reBf2Nn1k5mbZtltTiEELZHEn0d7T+9nz3Ze5gaMxUHZd1f56i2o/h9l9+z5MASvkj5wqqxCCFshyT6OlqYvBAPJw9ubX+rtUMB4LHYx+jfqj9/3/x39mbXc70YcYH//Oc/fPLJJ9YOQ4irkkRfBzklOXx75FtuaX8LXi5e1g4HACcHJ14b+hrBHsE8Ef+EXJxtQA8//DC/+93vrB2GEFclib4OVqSsoMJcwdROjTuk8mp83Xx5c/ibFJQV8FT8U3Jx9hrcdttt9O7dmy5dulQVGvvwww/p0KED1113HQ888ACPPvoocHFJYSFslYyjr6UKcwVLDixhQKsBtGvRztrhXCTGP4a/Dvwrz65/lte2v8af+/3Z2iHV2Kyts0jOTa7Xbcb4x/Bs32evut4HH3yAv78/JSUl9OnThzFjxjBz5kwSEhJo0aIFw4cPp1evXvUamxANTVr0tfRT+k9kFWdZbUhlTYxuN5q7O9/NouRFrExdefU3CP71r3/Ro0cP+vfvz7Fjx/j0008ZNmwYQUFBuLi4SO14cYFKcyVrjqyhqOLyRexsgbToa2lh0kJCvUIZEjrE2qFc0RO9nyA5N5m/b/o7Ub5RdA3sau2QrqomLe+GEB8fzw8//MCmTZvw8PBg2LBhxMTEkJSUZJV4hO1bnLyYWdtmMazNMOYOn2v1kXeXY5tR2bjk3GR2ZO1gasxUHB0crR3OFTk5OPHada8R6B7IEz8/QU5JjrVDsllnzpzBz88PDw8PkpOT2bx5MyUlJcTHx5OTk0NFRQXLli2zdpjCRuSV5jFv9zyC3IOIPxbPe3ves3ZIlyWJvhYWJi3E3cmd26Jus3YoNeLn5sebw98kvyyfp9Y+xZ7sPRRXFF/9jc3MqFGjqKyspHv37rzwwgv079+fVq1a8eKLLzJgwABuvPFGYmNjrR2msBH/3vVviiuKee+m9xjTbgz/3vVv1mest3ZYlyRliq9Rfmk+Ny6/kXHtxzFzwExrh3NNVh9ezZ/X/xmN8X8e6hVKtG800X7RRPlGEe0XTaRPJM6Ozo0eW1MpufvRRx+xfft23n77bWuH0mR+Z/boQO4BJq2exJSOU3i+3/OUVJbw229+y/Gi4ywZs4Q2Pm0aJY6alimuyeTgHwBjgSytdVfLMn9gCRAJHAUmaa3zlDHbxlxgNFAM3KO13lHbD2GLVqSsoMxU1uhVKuvD2HZj6R3cm6TcJFLyUkjNTyUlL4X1mesxWeZwd1JORLaIJNo3mii/qKp/Q71Cbbb/UYjGpLVm9rbZeLt480jPRwBwd3Lnn8P/yZTVU3gi/gkWjF6Au5O7lSM976oteqXUUKAQ+KRaop8N5GqtX1VKPQf4aa2fVUqNBv6Ekej7AXO11ledU6+ptOgrzZWM/nw0Yd5hfDDyA2uHU2/KTeUcOXOE1PzUquSfmp9KZmFm1TruTu5E+UZVtfzP/RvgFlAvs2lJ6/Taye/MOn5M+5En4p/gL/3+wpSYKRe89kvmLzzywyPc3PZmXh3yaoPONAf12KLXWq9TSkX+avGtwDDL44+BeOBZy/JPtHH02KyU8lVKtdJan6h56LZr7bG1nCg6wTN9nrF2KPXKxdGFjv4d6ejf8YLlheWFHDpzqCrxp+alsjZjLV+knq+j4+fqd0HLP9rXOAjU5k5hrXWD/2HYC1vocm2OykxlvLb9NaJ8o5jQYcJFrw8OHcyjvR7lrZ1v0S2wG3d1vssKUV6stsMrW55L3lrrE0qpYMvyUOBYtfUyLMsuSvRKqQeBBwHCwxu3hnttLUxeSIhnCMPaDLN2KI3Cy8WLHkE9LprMJKckh5T8FFLzzp8BfJH6BSWVJVXrtPJsVdXyj/KNooNfB9r7tsfJ4dJfOTc3N3JycggIqJ8zBHumtSYnJwc3Nzdrh9LsfJr4KZmFmbx303uX/S7f3+1+9p3ex+vbX6ejf0f6hPRp5CgvVt/j6C/1F3rJpofWej4wH4yum3qOo96l5KWw9eRWHo99/LL/wc1FgHsAAe4B9G/Vv2qZWZs5Xni8KvGn5BtnARuPb6TSbEyUHeUbxZxhc2jbou1F2wwLCyMjI4Ps7OxG+xxNmZubG2FhYdYOo1nJLs5m/p75DG8z/ILv/q85KAdeGfwKU7+eyvS101k6diktPVs2YqQXq23GOnWuS0Yp1QrIsizPAKpfbg4DjtclQFuxKHkRLg4u3BF9h7VDsUkOyoEw7zDCvMMuOOOpMFWQVpDG3tN7mZMwhymrp/DXQX9lVOSoC97v7OxM27YXHwCEsBVzd8yl0lzJ9LjpV13Xy8WLN4e/yZ1f38m0tdP4cOSHuDi6NEKUl1bbYRRfAXdbHt8NfFlt+e+UoT9wxh7658+UnWH14dWMbjcaPzc/a4fTpDg7OhPlF8Xt0bezbNwyov2ieXrt07y69VUptiaajH2n9/HloS/5beff1ni60Pa+7Xlp8Evsyd7Dq1tfbeAIr+yqiV4ptQjYBHRUSmUope4DXgVGKKVSgBGW5wDfAIeBVOA94JEGibqRrUxdSUllSZMcUmlLQjxD+HDkh9zV6S4+S/qMe9bcw4nCJt8OEHZOa82rW18lwC2AB7s/eE3vHRExgnu73suyg8v4POXzBorw6moy6uZy2e2GS6yrgT/WNShbYjKbWJy8mJ5BPekc0Nna4TR5zo7OPNv3WWJbxvLChheYuHoirw55lcGhg60dmhCX9M2Rb9idvZu/Dfwbns6e1/z+x3o9RmJOIi9vfpkOfh2sUm9K7oC5il8yfyGjMMOmq1Q2RSMiRrB4zGJaerTkkR8e4e2db2Mym6wdlhAXKK4oZk7CHDoHdObWqNrNIufo4MjsobMJdA/kyfgnyS3Nrecor04S/VUsTF5IkHsQN0bcaO1Q7E5ki0gWjF7ALe1v4d097/LQDw9J0TVhUz7c/yFZxVk81/e5Ot0Z7ufmxz+H/5O80jyeWftM1Ui0xiKJ/gqOnDnCxuMbmdhxIs4OjV//pTlwd3LnpcEv8beBf2NX1i4mrZrEzqyd1g5LCI4XHufDfR9yc9ub6RVc98lmOgd05oX+L7Dl5Bbm7phbDxHWnCT6K1iUvAgnBycmdpho7VDs3u3Rt7Ng9AJcnVz5/Zrf8/H+j+XuT2FVcxLmoFBM6z2t3rZ5a9StTO44mY/2f8Sao2vqbbtXI4n+MgrLC/ky9UtGRo4k0D3Q2uE0CzH+MSwZu4ThbYbz+vbXeTL+SQrKC6wdlmiGEk4l8N3R77i3672EeIbU67af7fMsPYN6MmPDDFLyUup125cjif4yvjz0JcWVxdwZIxdhG5O3izdzhs1hetx01h5by5TVU+p9/lghrsRkNjFr6yxCPEO4p+s99b59Z0dn3hj2Bp7Ono3WmJFEfwlmbWZx8mK6BnSle1B3a4fT7CiluLvL3Xww6gPKTGX85uvf8HnK59KVIxrFytSVJOUmMa33tAYrNRzsEcwb171B5tlM/rL+L5i1uUH2c44k+kvYdHwTRwuOypBKK+sV3IulY5cS2zKWmRtn8sKGFy4onCZEfTtbfpZ/7fwXvYJ7XVSmo77Ftozl6T5PE58Rz7t73m3QfUmiv4SFyQvxd/NnZORIa4fS7AW4B/CfG//Dwz0e5qtDX/Gbb37D0TNHrR2WsFPz98wnrzSPZ/s+2yhVVKfGTGVcu3G8s+sd1mWsa7D9SKL/lWMFx1ifsZ4JHSZYtQiROM/RwZE/9vwj826cR3ZxNlO+nsJ3R7+zdljCzqQVpLEgaQG3Rd1Gl4AujbJPpRQzBsygo39Hnlv/HOkF6Q2yH0n0v7LowCIclSOTOkyydijiVwaHDmbZuGW0923P9LXTmbV1lhRGE/Xm9W2v4+roymOxjzXqft2c3PjnsH/ioBx4Iv4JiiuK630fkuirKa4oZmXKSm6IuMHq9aPFpYV4hvDRyI+4q9NdLEhawD3f3cPJopPWDks0cRszNxKfEc+D3R+0ynDqMO8wZg+ZTWpeKi9uerHeBx5Ioq9m9eHVnK04K0Mqbdy5wmivX/c6qXmpTFw1kQ2ZG6wdlmiiKswVzN42mzbebbirk/Wm/hsYOpDHYh/j2yPf8mnip/W6bUn0FlprFiUvIsY/pl5udxYNb2TkSBaPXUygeyB/+OEPzNs1TwqjiWu29MBSDp05xNNxT1v9utx9Xe/jhvAbmJMwh20nt9XbdiXRW2w9uZXU/FTujLlT5ixtQtq2aMvCMQsZ134c7+x+h4d/eNgq1QFF05Rfms+8XfPo36q/TcwFrZTipUEvEe4TzvS10+utW1ISvcXCpIX4uvpyc9ubrR2KuEbuTu68NOgl/jrwr+w4tYOJqyayK2uXtcMSTcC/d/2boooinunzjM008M5NQ1hmKmNa/DTKTeV13qYkeowqdfEZ8YyPHo+bk5u1wxG1oJRifPR4ozCao1EY7ZP9n8jdtOKyUvJSWHpwKRM7TCTaL9ra4VygXYt2vDzoZfae3ss/tv6jztuTRA8sPrAYgMkdJ1s5ElFXnQI6sXjsYoaGDeW17a8xLX4aZ8vPWjssYWO01szaNgsvZy/+2NM2J8W7IeIG7u92P8sPLmfFwRV12ladEr1S6kml1H6l1D6l1CKllJtSqq1SaotSKkUptUQpZdN3HZVWlvJ5yucMbzOc1l6trR2OqAc+Lj68OfxNpsdN5+djPzNp1SQ+T/mc0spSa4cmbMTPx35my4kt/LHnH/F187V2OJf1aM9HGdh6IC9veZm92XtrvZ1aJ3qlVCjwGBCnte4KOAJTgFnAP7XW0UAecF+to2sE3xz5hjNlZ2RIpZ2pKow28gM8nT2ZuXEmNy2/ibd3vk12cba1wxNWVG4q5/Xtr9O+RXsmdbTtGyMdHRyZNWQWwR7BPBn/ZK1nYKtr140T4K6UcgI8gBPA9cByy+sfA7fVcR8NRmvNwqSFRPlG0Sekj7XDEQ0gtmUsy8Yt44ORH9AjuAfz98znphU38Zdf/kJSTpK1wxNWsCBpAcfOHuOZvs/g5OBk7XCuytfNl38O+yf5Zfk8ve7pWk1DWOtEr7XOBF4H0jES/BkgAcjXWp+LJAMIre0+GtqOrB0cyDvA1JipNnPFXdQ/pRR9Qvrw1vVvsfr21UzuOJkf0n5g0upJ3LPmHn5M/1HG3zcTp0tO8+7udxkWNoyBrQdaO5wa6xTQiZkDZrLt5DbeTHjzmt9fl64bP+BWoC3QGvAELjU28ZLDHpRSDyqltiultmdnW+dUemHSQrxdvBnbbqxV9i8aX7hPOM/1fY7/Tfwf0+Omc6LwBE/8/ARjvxjLgsQFFJYXWjtE0YD+teNflJvLmd5nurVDuWbj2o9jasxUPk78mG+PfHtN761L182NwBGtdbbWugL4HBgI+Fq6cgDCgOOXerPWer7WOk5rHRcUFFSHMGrnh7Qf+DH9R26Puh0PZ49G37+wLh8XH+7ucjdfj/+aN657gyCPIGZtm8WNy29k1tZZZJzNsHaIop7tz9nPytSV/LbTb4nwibB2OLXydNzT9AruxcyNMzmYd7DG76tLok8H+iulPJTR73EDkAj8DEywrHM38GUd9lHvtNa8u/tdnox/ki4BXXig2wPWDklYkZODEzdF3sQnN3/CojGLuC7sOhYnL2bMF2N44ucnSDiVIGPx7YDWmllbZ+Hn5seD3R+0dji15uzozBvXvYGXsxdP/PxEjd9Xlz76LRgXXXcAey3bmg88C0xTSqUCAcD7td1HfSutLOXZdc/y9q63GdtuLB+M+sCmh1aJxtU1sCuzhs5izR1ruLfrvWw/tZ171tzD5NWTWXVolZREbsLWHF3DzqydPB77OF4uXtYOp06CPIKYM2wOJ4pO1Pg9yhZaK3FxcXr79u0Nuo+s4iwe++kxEnMSeSz2Me7rep9cgBVXVFJZwurDq1mQuIDDZw4T6B7IlI5TmNRxEn5uftYOT9RQSWUJ474Yh7+bP4vGLMLRwdHaIdWLZQeXManjpAStddzV1m0WiX7f6X08/tPjnK04y6tDXuX68OsbbF/C/pi1mU3HN/Fp4qdsOL4BV0dXxrYby12d7iLKL8ra4YmreGfXO8zbPY+PRn1E75a9rR1OvVJK1SjR2/4g0jpac2QN/7fh/whwC+DTmz+lo39Ha4ckmhgH5cCg0EEMCh3EofxDLEhawKpDq1iRsoIBrQZwV+e7GBw6GAclFUVszYnCE3yw7wNGRY6yuyR/Ley2RW/WZubtmse7e94lNjiWOcPmEOAeUK/7EM1Xfmk+y1OWsyhpEVklWUT6RHJXp7sY136cjOKyIc+sfYafjv3EqttW0cqrlbXDqXc1bdHbZaIvrijm/zb8H/9L+x+3Rd3GC/1fsPqEAsI+VZgq+D7tez5N/JT9OfvxcfFhQocJTI2ZSohniLXDa9Z2nNrB3Wvu5uEeD9ts4bK6araJ/mTRSf700584mHeQab2n8bvOv5OLrqLBaa3Zlb2LTxM/5cf0H1Eoboq4ifu7308Hvw7WDq/ZMWszU7+eSk5JDl/d9pXdnmU1yz763dm7efynxykzlfHW9W8xNGyotUMSzYRSil7BvegV3IvMwkwWJS1iRcoKvk/7nqkxU3mk5yN4u3hbO8xm48vUL0nMSeTVIa/abZK/FnZz9WjVoVXcu+Ze3J3cWTB6gSR5YTWhXqFM7zOdb8d/y/jo8XyW9Bm3rLyF1Ycc29InAAAgAElEQVRXy81XjaCwvJC5O+bSM6gno9uOtnY4NqHJJ3qzNvPPhH/y51/+TI/gHiwas4j2vu2tHZYQ+Lr5MmPADBaNWUSIRwjPr3+e33/3+2u6dV1cu/l755NTmsOzfZ+VbluLJp3oiyqKePynx/lg3wdM7DCRd0e8K3e6CpvTJbALn435jJkDZpKan8qkVZOYvW22FFBrAOkF6SxIXMCt7W+la2BXa4djM5psos8szOS33/6W9Znreb7v87zQ/wWcHZytHZYQl+SgHJjQYQKrb1vN7dG3syBxAeNWjpPunHr2+vbXcXZw5vHYx60dik1pkol+x6kdTF09lZOFJ5l34zzu7HSnnKKJJsHXzZeZA2aycMxCWnq0rOrOSclLsXZoTd6m45v4+djPPND9AYI8Gr8iri1rcon+i5QvuO/7+2jh2oLPxnzWpCYPEOKcroFd+Wz0Z8wYMIPU/FQmrpoo3Tl1UGmuZPa22YR5hfHbzr+1djg2p8kkepPZxGvbXmPGxhnEtYxjwegFtG3R1tphCVFrjg6OTOww8aLunK8Pfy3dOddAa817e98jNT+V6XHTcXV0tXZINqdJJPqz5Wd59KdH+STxE+6MuZN3bnyHFq4trB2WEPXiXHfOZ6M/o6VHS55b/xz3fncvqXmp1g7N5hVXFPPc+ueYt2seIyJGSMHCy7D5O2OPFRzj0Z8eJb0gnef7PW/zs7YLURcms4nPUz9n7o65FJYX8ptOv+EPPf7Q5GuoN4SUvBSeWvsUaQVpPNrzUe7rdl+zKyxX0ztjbfq3svXEVqZ+M5Wc0hzeHfGuJHlh985156y6bRW3Rd3Gp4mfcsvKW6Q751e+OvQVd359JwVlBbw34j0e6P5As0vy18JmfzNLDyzlof89RIBbAItGL6Jvq77WDkmIRuPn5seLA19kwegFBHkESXeORWllKTM3zuQvv/yFbkHdWH7LcskNNWBzXTfnrp4vSl7E4NDBzB46W2qEiGbNZDaxImUFc3fMpbii2OjO6fkHPJ09rR1ao0orSOOp+Kc4kHeAB7o9wCM9H8HJwa7KdV2zJlm98kzZGaavnc7mE5u5u/PdPNn7SbuZ9kuIusorzWPujrmsSFlBkHsQ0+Omc3Pbm5vFPSTfH/2eGRtn4OTgxD8G/4MhYUOsHZJNaJREr5TyBf4LdAU0cC9wAFgCRAJHgUla67wrbScuLk4v+3EZf/rpT2QWZjKj/wxuj7691nEJYc/2ZO/h5S0vk5iTSJ+QPvy575/tdkrDClMFbyS8wWdJn9E9sDuvX/e6XU4gUluNleg/BtZrrf+rlHIBPIA/A7la61eVUs8BflrrZ6+0nZgeMdr/OX+clBNvDn+T2JaxtY5JiOagOXTnHC88zvS109l7ei93dbqLab2n4ewoZU6qa/BEr5TyAXYD7XS1jSilDgDDtNYnlFKtgHit9RUnavVo66FHvjmSt294m1Cv0FrFI0RzlFuay9wdc/k85XOC3YOZ3mc6oyJHNfnunHUZ6/jzL3/GZDbxt0F/Y0TECGuHZJMaI9H3BOYDiUAPIAF4HMjUWvtWWy9Pa+13pW217NhSH9532K5aI0I0pt3Zu3l588sk5SbRN6Qvz/d9vkl251SaK/n3rn/z373/paNfR+YMm0O4T7i1w7JZjZHo44DNwCCt9Ral1FygAPhTTRK9UupB4EGA8PDw3mlpabWKQwhhMJlNLD+4nLk7jZutBoUOYkrHKQwOHdwkBjVkF2fzzLpn2H5qO3dE38FzfZ/DzcnN2mHZtMZI9CHAZq11pOX5EOA5IIpr7Lqp78nBhWjOcktzWZy8mOUHl5Ndkk1rz9ZM7DiR8dHj8Xfzt3Z4l7TlxBaeWfcMJZUlvND/Bca1H2ftkJqExroYux64X2t9QCn1InCu7yWn2sVYf631M1fajiR6IepfhbmCn9N/ZsmBJWw9uRVnB2duiryJKR2n0COoh03045u1mff2vMe83fOI8IlgznVzmmSXk7U0VqLviTG80gU4DPwe427bpUA4kA5M1FrnXmk7kuiFaFiH8g+x5MASvjr0FUUVRcT4xzC542RGtx1ttcmz80rzeH7982w4voHRbUczc8BMmcj7GjXJG6aEEA2ruKKY1YdXs+TAEg7mHcTL2Ytbo25lUsdJtGvRrtHi2JW1i+lrp5NbmstzfZ9jYoeJNnGG0dRIohdCXJbWml3Zu1icvJjv076n0lxJv5B+TI6ZzPA2wxustIDWmk8SP+HNhDdp6dmSOcPm0Dmgc4PsqzmQRC+EqJHTJadZmbqSpQeWcqLoBMHuwUzoOIEJ0RPqdUq+gvICZmyYwY/pP3J9m+v5++C/4+PiU2/bb44k0QshronJbGJ95noWH1jMhswNOCknrg+/nikxU4hrGVenrpXEnESein+Kk0UneaL3E/yu8++kq6YeSKIXQtRaekE6Sw8s5YvULygoL6Bdi3ZM7jiZce3HXVM1Wa01yw4uY9bWWfi6+fLGdW/QM7hnA0bevEiiF0LUWWllKWuOrmFJ8hL25ezD3cmdse3GMrnjZDr6X/H2GIorivnrpr/yzZFvGNR6EK8MecVmx/E3VZLohRD1at/pfSw5sIRvj3xLmamM2OBYJneczI0RN+Li6HLBuofyDzEtfhpHC47ySI9HZAaoBiKJXgjRIM6UnWFl6kqWHFjCsbPH8Hfz547oO5jYYSKtvFqx6tAq/r7577g7uTN76Gz6tepn7ZDtliR6IUSDMmszm45vYvGBxazLWAdAjH8MiTmJ9G7Zm9lDZxPsEWzlKO1bTRN9856HSwhRaw7KgUGhgxgUOojjhcdZfnA5P6T/wP3d7uePPf/Y7Kf5syXSohdCiCaqpi16uToihBB2ThK9EELYOUn0Qghh5yTRCyGEnZNEL4QQdk4SvRBC2DlJ9EIIYeck0QshhJ2TRC+EEHauzoleKeWolNqplFpted5WKbVFKZWilFqilHK52jaEEEI0nPpo0T8OJFV7Pgv4p9Y6GsgD7quHfQghhKilOiV6pVQYMAb4r+W5Aq4HlltW+Ri4rS77EEIIUTd1bdG/CTwDmC3PA4B8rXWl5XkGEFrHfQghhKiDWid6pdRYIEtrnVB98SVWvWR5TKXUg0qp7Uqp7dnZ2bUNQwghxFXUpWD0IOAWpdRowA3wwWjh+yqlnCyt+jDg+KXerLWeD8wHo0xxHeIQ4pJKyk2k5xaTfbYMN2cHPFyc8HBxxMPVEU8XJ9ydHXFwuFTbRAj7UutEr7V+HngeQCk1DJiutf6NUmoZMAFYDNwNfFkPcQpxEa01+cUVpOUWk5ZTRHpOcdXjtJxiss6WXXUb7s6OeLo64u5iJH8PF8eqA4Knq5NluWO1g4ST5bmxzNPVEXdn499z68gBRNiahpgC5llgsVLqJWAn8H4D7EM0E2az5kRB6QWJ3PjXSOZnSysvWL+ljysR/p4M7RBEhL8H4QEehPi4UW4yU1Rmori8kuJy49+iMhMlFSaKyoxlRWWVVc9PF5ZdtN618Kh2MDj32NP14gOJx1UOGp4uTni4OlYdQIzxDkJcm3pJ9FrreCDe8vgw0Lc+tiuah9IKExl5xaTlGD/p51rlucVk5JZQbjJXrevsqAjz8yDc34PYcD/C/T2ICPAkIsCDNn4euLs4NkiMZrM2DgLllZSUm6oOGkXlJkosB4NzB5GichPFZdVeKzdRUm6isKySrIIyiisqKS4ztlVaYb76zi2UAg9n46yi6uDgUu1sxHJA8HRxIsDLhZ5t/Oge1gI354b5nYimQyZ1FI2iqKySw9lFVS3xc63y9JxiThSUUn1GS08XR8IDPOkQ7M2ITi0JD/AgMsCTcH8PWvu642iFbhEHB4WnqxOervX7J2OyHEDOHRiqDhZllgNKtWUXr2M8PltayamC0gvOUMoqjQOIs6OiS+sWxEX40dvyE+zjVq+fQdg+SfSi3mmtycwvISEtr+on6UQB5mrJPNDLlYgAD/q3CyA8wIOIAA/C/Y2WeYCnS7PponB0UHi5OuFVzweQ3KLyqt/9jrQ8Pt2cxn9/OQJAG393eoefS/z+dAzxtsrBUzQemRxc1FmFyUzi8QK2W5JKQloeJwtKAaOvule4L70j/OncyseS0D3qvWUsrqy80sz+42eqkv/2tDyyLRervVyd6BXuS6wl+fcK98XbzdnKEYuaqOnk4JLoxTXLLy6/oLW+OyO/qq851Ned3hF+xEX6ERvuR0yIN06OUjvP1mitycgrsST9XBLS8jlw0jjrUgo6tvQmLtLS6g/3p42/e7M5y2pKJNGLeqG15vDpIhKO5lUlhUPZRQA4OSi6tPahd4R/Vf9vSAvp/22qzpZWsOtYftUBfGd6PoVlxqimIG/X8909kX50ae2Dq5Nc5LW2miZ6OX8WFyitMLH7WD4J6XkkHM1jR3oeecUVAPh6ONM73I/xsWHERfjRPcy3wUa5iMbn7ebMkOgghkQHAcaF4oOnzlZ1yW1Py2XN/pMAuDg50COsBbERflUHgAAvV2uGL65AWvTN3KmCUqOlfjSPhPQ89meeodJy1bR9kGdVS713hD/tAj3lRqBmLsvyfUlIM74v+zLPUGEyvi9tAz2JDfdjYPsAhkQHyuieRiBdN+IixeXGEMed6cbFuIS0PDLySgBwdXKgRxtfo389wo9e4X74e8pUAuLKSitM7M08c76xkJZbdQYYE+LN0A5BDIkOpE+kv4znbwCS6JshrTW5ReXn7x6tNlY9zVLz5Zxgb1fLxTajf71zKx9cnOSiqagbs1mTeKKA9SmnWZ+SzfajeZSbzLg6OdC3rT9Do4MY0iGQji295eJuPZBEb6dMZs3x/BLL3aPVErnljtJzF8/OadXCzXL36Pk7SHuE+RLmJ6MoRMMrLq9ky5Fc1h3MZn3KaVKzCgGjoTEkOoihHQIZFBVIoPTv14ok+iastMLEsapEXky6pRxAek4xx/KKq/pEwbjzsY2fUdMlolo5gIgAD8L8POR0WdiU4/kl/JJymnUp2fySepp8SzdPl9Y+RuKPDqR3pJ+M6KkhSfQ2Lr+4/MJEXq1g17mbjc7xdnW66O7RcwW7WrWwTkkAIerKZNbsP36G9SmnWXcwm4S0PCrNGndnR/q182dIdBDXdQikfZCXnH1ehiR6G5WWU8TTy/ew9UjuBcuDvV0vTOSWO0gjAjzx83CWL7qwe4VllWw5nFPVzXP4tHG/RqsWbgyJDmRIdBCDogJlkEA1kuhtjNaaz7ak88o3STgqxYND29EhxLsqoXu4yC0NQlR3LLeYX1KNi7q/pJymoLQSpaBbaIuqxB8b7tesBxFIorchJ8+U8uyKPaw9mM3gqEBmT+hOa193a4clRJNhMmv2ZORXjebZkZ6PyazxcHFkQLsAhnYI4roOQUQGelo71EYlid4GaK35avdxXli5j3KTmT+P7sRd/SLkpiMh6qigtILNh3JYl2J086TlFANGjZ6RXUMY1SWETq3sfwinJHoryy0q5/9W7uWbvSfpFe7LGxN70C7Iy9phCWGX0nKK+DEpi+/2n2Tb0VzMGsL9PRjVNYSRXVrSq42fXTawJNFb0Y9Jp3h2xV7OlJTzxI0deGhoO6ngKEQjOV1Yxg+Jp1iz/yQbUk9TYdIEe7tyU5eWjOrSin7t/HG2k79HSfRWcLa0gr+vTmTp9gxiQryZM6knnVv7WDssYS2V5eDgaPwIqygoreDnZKOl/3NyNiUVJlq4O3NDp2BGdQlhaIegJn2vSYMneqVUG+ATIAQwA/O11nOVUv7AEiASOApM0lrnXWlb9pDoNx3KYfqy3Zw4U8JD17XniRuj5aaP5ubsKTi2GY5thWNb4Pgu0CbwCADPYPCy/HgGgVfLix97BMhBoQGVVphYn3KaNftO8kPSKc6UVODu7MiwjkGM6hrC8JhgfJrYhCuNkehbAa201juUUt5AAnAbcA+Qq7V+VSn1HOCntX72Sttqyom+tMLE7DUH+GDDESIDPHhjUg96R/hbOyzR0MwmyEo0EvqxrZC+GfLTjNccXSE0FsL6gJMrFGZBUTYUnoLCbCjKgsrSi7epHMAjsNoBIRi8LAeCXz/28JeDQh1UmMxsOZzLd/tP8t3+k2SdLcPZUTEoKpCRXUIY0bllkyjL0OhdN0qpL4G3LT/DtNYnLAeDeK11xyu9t6km+t3H8pm2dBeHsov43YAInrs5RsbD26vSAsjcfj6pZ2yH8rPGa57BEN4P2vSDNv2hVXcjwV+O1lBWcD7pVz8AXPDY8mMqu3gbysE4G7jcmYJ/e+NgY+ejTuqD2azZeSyf7/afZM2+k6TnFuOgIC7Sn1FdQhjZNYRQGx0O3aiJXikVCawDugLpWmvfaq/laa39LvGeB4EHAcLDw3unpaXVOY7GUmEy89aPKfw7/hBBXq68NrF71WQNwg5obbTOzyX1Y1shaz9oM6CgZRdo09dI6m36gl9kwyXUqoOCJelXPwBc8NhyxmAqP//eoE7Q5z7oPhnc5FpRTWitST55ljX7jJZ+8knjYN4ttIVlBE8IUcG2M3qu0RK9UsoLWAu8rLX+XCmVX5NEX11TatEfPHWWaUt3sS+zgPG9Qpl5SxdauDetfj3xK5XlcHKPJalbumIKjZmUcPGCsLjzST0sDtxaWDfey9EaSs8YST99M2x/H47vBGdP6D4R4u4zzjZEjR09XWS09PefZGd6PgBRwV6MtIzg6RrqY9Wx+o2S6JVSzsBq4Dut9RzLsgPYYdeNyax5/5fDvP79QbxcnXjl9q6M6trK2mGJ2ijKgYxqrfXjO873mfuGn0/q4f0huHPT7gvP3GEk/L0roLLEuG4Qdx90uR2cZQaoa3HyTCnfJxot/c2HczGZNaG+7ozsEsItPVvTI6xFoyf9xrgYq4CPMS68PlFt+WtATrWLsf5a62eutC1bT/TpOcVMX7abrUdzGdG5Ja/c3o0gb9u/UCMwWrmnD55P6sc2Q06q8ZqDM7TqYfSth/eDsL7gY6cH75I82LUItn8AOSng7ge97oLev4eA9taOrsnJKyrnh6RTfLf/JOtSTlNeaaZdkCfje4VyW69Qwvw8GiWOxkj0g4H1wF6M4ZUAfwa2AEuBcCAdmKi1zr3kRixsNdFrrVm09RgvfZ2Io1LMvKULd8SG2v1t1U2e2Wy02JNWQdJXkJ9uLHf3P5/U2/SD1r3A2TYvsjUYreHIOqOVn/w1mCuh/fVGK7/DKHCUwQTXqqC0gm/3nmDFjsyqqrT92vpzR2wYN3cLwbsBh2zKDVN1dKrAKEQWfyCbQVEBzJ7Qw2avvAuMfvaj643knvy1caHS0QXaDYOOoyFyMAREySiU6s6ehB2fQMJHUJAJPqEQezfE/s5+z2wa2LHcYlbuzOSLnZkcPl2Eq5MDN3UJYXyvUIZEB9b7HfKS6OvgXCGyskoTz42K4XcDIu2yTkaTV14Mh34ykvvBb40Lkc6eED0COo2D6JtktElNmCrh4BqjlX/oJ3BwgpgxRiu/7VA5ONaC1ppdx/L5YmcmX+0+Tn5xBYFertzSozXjY0Pp0rp+LuJKoq+FvKJy/u/LfXy95wQ92/gyZ5IUIrM5JfmQ8r3RJZPyg3GB0d3PaLXHjIX2w5tfd0x9yjkECR/CzgVGv35ANMTdCz2nGr9ncc3KK83EH8jii52Z/JiURbnJTIeWXtzeK4zberWmVYvaf18l0V+jn5OzeGbFHvKLpRCZzSnMMrpjklYZ/cvmCvBuZbQ6O42DiEHgKENc61VFKSSuhG3vG9c7nNyh2x1GKz801trRNVn5xeV8vfcEn+/IJCEtD6VgYPsAxvcKY1TXEDxdr+0aiST6Giosq+Sl1Yks3naMji29mTO5B11a2+g46eYkLw2SV0PSakjfBGjwa2sk9k63QGhvcJADcaM4scfo1tmzDCqKjIvYcfdB1zvApXFGl9ijo6eL+MLSn5+eW4y7syOjuoZwe69QBkUF1mguaEn0V2E2azYdzuHZFXs4nl/Cg0Pb8+QIKURmVdkHjC6ZpFVwYrexrGVXI7nHjDXuSJX+YuspPQN7lhqt/Owk48axHncaXTtBHawdXZOltSYhLY/Pd2ayevdxCkoraenjyq09Q7m9VyidWl3+OpMk+l8pKTexOyOfhLQ8EtLy2JGeR35xBREBHsyRQmTWobVx52bSKuMnJ8VYHtYXOo01kruM8bY9WkPaRqOVn/iV0ZUWOQT63G90p0k3Wq2VVpj4OTmLFTsyiT+QRaVZ06mVD+N7hXJrz9YE+1x4k1uzT/Qnz5SSkJbH9rRcdqTlsf94AZVm47NGBXvRO9yP3pF+jO3eSgqRNSazyeiKSVpldMsUZIByNIY/dhpnJAqf1taOUtRUYRbs/BS2fwRn0sErxEj4fe4zKmyKWsspLGP1nhN8vjOT3cfycVAwODqIO2JDualzCO4ujs0r0VeazCSfPFvVWk9IyyMzvwQAN2cHeoT50jvCj7hIP3q18cPP06W+Qhc1YTbDkbWw/3NI/gaKTxulfKNuMJJ7h1GSFJo6swlSf4Ct70Hq/8DZA3r9FgY8YhR9E3WSmlVYNT4/M78ETxdHbu7Wijcm9bTfRF9QWsHO9HwSjuaSkJ7HrvR8ispNALT0cSUuwp/eEX70jvCjc2sfu5k2rMkpzoVdnxm33eceBhdv6DDS6JaJGgGuMnTVLp1KhE1vG/352gSdb4WBj8lonXpgNmu2Hs3l8x0ZfLP3JPv/Nso+Er3WmvTcYrYfzSMhPY+Eo3kczDqL1uCgoFMrH+Ii/Ii1JPZQX3cpUWBNWhu12re/D/s+N2qpt+lvnMp3ukUKaTUnBcdhy39g+4dGqeXIIUbCj7pRRkzVg5JyEx6uTk0z0ZdVmtiXecboXz9qXDQ9XWjU2PZ2daJXhB9xlqTes43vNY87FQ2kvAj2LjNGZJzcY5T37T7ZGJER0tXa0QlrKi0wSi1snmeUWgiKgYF/gm4TrzxBi7iqJtVH36FLD/371xazPS2PvRlnKDcZNdIiAjyqumDiIvyJDvaSUgS2JivZaL3vXmy02lp2NZJ790ng6m3t6IQtMVUYZ3kb/wWn9hkXbvs/bFTQdPe9+vvFRZpUondtFa0j7v0XXUN9iIv0JzbcSO5SCthGVZYb4923fwBpG4ziYZ1vM7pn2vSTse7iyrQ2aups/BccjjfO/nrfA/0eBt821o6uSWlSib5z9556R0ICbs5ys5JNy083Kh3u+MSYxcg3wmi997oLPAOtHZ1oik7sgY1vwb4VRgOhy3ijW0dmwqqRJpXobaHWjbgMswlSfzS6Zw5+Z/wxdhhl3ALf/nq5qCbqR/4x2PwO7PgYyguN8tIDHzO+Y3KGeFmS6EXdFGYbN8IkfGi05D2DjTrlve+R02vRcEryje/c5v8Y8/a27Ga08LuOlztuL0ESvbh2Wp+fVDrxSzCVG0Pi4u41yhE4yY1mopFUlhmjuDa+BdnJxqQo/f9gTIwicwxUkUQvaq60APYsMS6uZiWCawuj/njcvRB0xXndhWhYZrNxp+2Gf0HaL8Z3M+4e6PcHmQULSfSiJk7uNca9711m9Iu26mHUKel6B7h4Wjs6IS6UmWC08BO/NOojdZ9kdOsEd7J2ZFZT00TfYHcbKaVGAXMBR+C/WutXG2pf4hpUlBp/KNvfh2NbwMnNSOznJpSQC1/CVoX2hokfQe4R4+arnQuMEhtRI2DQY0Y3o3x/L6lBWvRKKUfgIDACyAC2AVO11omXWl9a9A2ossyo7Z6+2UjsaRssU8RFGV0zPaZKQTHRNBXnGmekW981hvsGREPEQAjvb9zP4d/O7hO/tVv0fYFUrfVhSzCLgVuBSyZ6UY8Ks42EfmwLHNtq1Hs3lRmv+beDDjdDj8nQ9jq7/yMQds7DH657GgY+atyZnfw17F9pDNEE8Ag0En54P+PfVj2bba2lhkr0ocCxas8zgH7VV1BKPQg8CBAeHt5AYdg5s9kYkXAuqR/bbFSJBONu1da9oN9Dxpe8TV/wCrZuvEI0BGd3iPu98WM2w+kDljNYy9/Ega+N9RxdjGTfpu/5Vn8z+ZtoqER/qabiBX1EWuv5wHwwum4aKA77UlZoXJCqarFvg7IzxmueQcYXt/fvjX9b95SCUaL5cXAwLs4GdzISPxhnuRlbzyf/rfONMspgzENcvdUfFAMO9neHfkMl+gyg+l01YcDxBtqXfdIazmRUS+pb4OQ+o743yvgidx1//kvq11a6YoS4FK8gY+aymDHG819ftzr0I+xZbLzm6gNhfc7/XYX2tovifA2V6LcB0UqptkAmMAW4s4H2ZR9MFcZwx3NJPX0LnLUcG509ISwOhjxl+fLFSbU/IWrLydXovmnT13iuNeQdMVr751r98f8ANCgHY1L6Nv3PJ/8WbZpco6pBEr3WulIp9SjwHcbwyg+01vsbYl9NVnEuZGw7n9QzE6DSmP6QFuHG6IFzX6zgLuAodfeFaBBKGQMV/NtBjynGspJ8yNxu/G0e2wK7FsK294zXvFtZrntZ/j5Dutt8eQa5YaqxVJQYk2If+gkO/WzU4wZwcDK+KOe+NGF9oUWodWMVQlzIVAlZ+y9s9Z9JN15z9oCON0P3KUYRtkZslMmdsdamtVFO4NBPxk/aRqgsNa78hw+AtkONK/+tY8HFw9rRCiGuVcFxo7V/ZB3s/8K4P8UzGLpNMM4MQro3eBePJHprKMw2JlI4l9wLTxrLg2KMI337G4wuGUnsQtiXynJI+d64qHtgDZgrIKiTkfC7TwKf1g2yW0n0jaGyzDiNO5fYT+4xlrv7Q/vhRnJvN1y6YoRoTopzjRb+niVGix9lnMH3mAqdxoGrV73tShJ9Q9Aasg9U647ZABXFRj97m/5Gco+6AUJ6yIQcQgjIOQR7lsLuRZCfZvTndxoH3Scbk6vUccy+JPr6UpQDR+LPX0QtyDSWB0RbujeMg7UAAAhPSURBVGOuh8hBdjHWVgjRQLQ2Wve7Fxmt/dIzxuidbhOMln7LLrXarCT62qosN+6iO9dqP74L0ODmaxyB219vtNx9pWyDEKIWKkrh4BqjayflezBXGjNp9ZgM3SaCd0iNNyWJvqa0hpzU84n9yHqoKDLqXbfpe77V3rqXXd4aLYSwoqLTsO9z4yJuZoJxg1a74UYrP2bMVQduSKI/R2soKzBGxBRlQeGp84/PZMLRX86Ph/VvV607ZjC4tWiYmIQQ4teyDxqt/D1L4MwxcPGCzrca/fmRQy553c++E73WUHbWqEFdeAoKsy7z2JLQK0sv3oZyMAqBhfU5n9z929bfhxJCiNowmyF9o6U//0soP2vMmdt9knFTVnBM1apNL9Fv22ZMZ1eYZUnWWVd+fLnk7RFolB71DAKvlkZBI8/gix97+EtXjBDCtlWUwIFvjHr7qT8aRQ1b9TTG53edgPIObkKJvo2H3v6Qz/laLxdQ4BloSdCW5O0ZZCTzqsctjeceAZK8hRD2qTAL9i43+vNP7AbliHoxz7pzxl4TF0/oc5+lJV49oVuStxT0EkI0d17BMOAR4ycryWjl87cavdU2WvS2NLxSCCGaiJr20cvtm0IIYeck0QshhJ2TRC+EEHZOEr0QQtg5SfRCCGHnJNELIYSdk0QvhBB2ThK9EELYOZu4YUopdRY4YO04GlkgcNraQTQy+czNg3zmxhOhtQ662kq2UlvgQE3u7rInSqnt8pntn3zm5sHWP7N03QghhJ2TRC+EEHbOVhL9fGsHYAXymZsH+czNg01/Zpu4GCuEEKLh2EqLXgghRAOpVaJXSrVRSv2slEpSSu1XSj1uWe6vlPqfUirF8q+fZXmMUmqTUqpMKTX9V9t60rKNfUqpRUopt8vs827LdlOUUndXW/6yUuqYUqqwNp+liX7mNUqp3ZZt/Ecp1SDTatnYZ45XSh1QSu2y/ATb82dWSnlX+6y7lFKnlVJv2vNntiyfrJTaY9nG7Ib4vFb8zGuUUvlKqdW/Wv6oUipVKaWVUoEN8oG11tf8A7QCYi2PvYGDQGdgNvCcZflzwCzL42CgD/AyML3adkKBI4C75flS4J5L7M8fOGz518/y2M/yWn9LPIW1+SxN9DP7WP5VwApgSjP4zPFAXEP+H9vaZ/7VegnAUHv+zEAAkA4EWdb7GLjBHj6z5bUbgHHA6l8t7wVEAkeBwIb4vLVq0WutT2itd1genwWSLB/4Vst/zrn/pNss62RprbcBFZfYnNP/t3d3IVKVcRzHv3/aEnUhrYvNXsDeYKG3vdJeDMrEDQkRIsgsoi6CvJCug2C7i4oguuhmKyi2EERqQ8Jsu8gELULRrcxEpSyp1V4UKsn238X/mTrp7LgzOzPn6fj7wLA7Z87bT8b/Oc8553kWmG1mPcAc4Ps68wwCW9z9J3f/GdgC3J3Wvd3dj7SSoxmZZT5eWM8FQEdutOSUuVtyzGxm1xKFZusM49WVUeargH3uPpHm+wC4tw0Rz1BCZtx9DDhRZ/pOdz80o0BnMeNr9Ga2kDgi7QD6akU3/WzYvHb374DniaP4EeBXd3+/zqyXAd8W3h9O00qRQ2Yz2wz8SHxxNrQYZdpyyAy8li5jPGVm1mKUacskM8BqYL2n079OKjnzfqDfzBamorkKuGImeaajS5lLNaNCb2a9xKWDJwpnmc0sP584gl4JXArMNbMH681aZ1opjwvlktndB4nm5yxgabP70YxMMq9x9xuA29ProWb3oxmZZK65H3ir2X1oVtmZ09n948B6ovVyCDjV7H40o4uZS9VyoTez84l/oBF335gm/2BmC9LnC4gzzkaWAQfdfcLd/wQ2Area2eLCTaiVxBG/eGS/nCmaR52UW2Z3/wMYJb5oHZFL5nTmVGtmvwksak/CM+WSOW3rJqDH3T9rS7gp5JLZ3d9198Xufgsx/tXX7cp4ui5nLlWrT90Y8Arwpbu/UPhoFKjdQX8YeOcsq/oGuNnM5qR13pXWucPdB9JrFNgMLDez+ekIujxN65pcMptZb+GL2AOsAPa2K2dRRpl7ak8jpP+c9wDj7cpZlEvmwnpW0+Gz+ZwyW3qaKk1fCwy3J+V/lZC5XN7aHeslRPNyN7ArvVYQd83HiKPwGHBRmv8S4ih+HPgl/V57cuRpolCNA28As6bY5qPENbz9wCOF6c+m9U2mn0OtZPq/ZAb6gE/TfnwOvESc8VU581ziqZNa5heB86qcufDZAaC/E1lzzEwc1L5Ir448TVZi5q3ABPB7Wn4wTV+X3p8iWjbD7c6rnrEiIhWnnrEiIhWnQi8iUnEq9CIiFadCLyJScSr0IiIVp0Iv0gIzm2dmawvv77DTRiUUyYUKvUhr5hEdekSyp0IvlZcGydprZsMWY4aPmNkyM9tmMe74IotxyN+2GAt9u5ndmJYdMrNXLcbDP2Bm69JqnwGuTl3cn0vTes1sQ9rWSOopKVK6nrJ3QKRLrgHuAx4jehY/QPSOXAk8SYymuNPdV5nZUuB1YCAt2w/cSYxb/pWZvUyMVX69uw9AXLohRkC8jujduA24Dfi4G+FEGtEZvZwrDrr7HnefJIZRGPPoFr6H+KMPS4ju67j7h8DFZnZhWnaTu59096PEIFd9U2zjE3c/nLaxK61XpHQq9HKuOFn4fbLwfpJo2TYaLri47F9M3RKe7nwiXaVCLxI+AtbAP5dhjnrj8clPEJdyRLKnMw6RMET8BavdwG/8O1RtXe5+LN3MHQfeAzZ1fhdFWqPRK0VEKk6XbkREKk6FXkSk4lToRUQqToVeRKTiVOhFRCpOhV5EpOJU6EVEKk6FXkSk4v4GsPIi+oY43AcAAAAASUVORK5CYII=)]

十六、Pandas的分层索引MultiIndex

为什么要学习分层索引MultiIndex?

  • 分层索引:在一个轴向上拥有多个索引层级,可以表达更高维度数据的形式;
  • 可以更方便的进行数据筛选,如果有序则性能更好;
  • groupby等操作的结果,如果是多KEY,结果是分层索引,需要会使用
  • 一般不需要自己创建分层索引(MultiIndex有构造函数但一般不用)

演示数据:百度、阿里巴巴、爱奇艺、京东四家公司的10天股票数据
数据来自:英为财经
https://cn.investing.com/

本次演示提纲:
一、Series的分层索引MultiIndex
二、Series有多层索引怎样筛选数据?
三、DataFrame的多层索引MultiIndex
四、DataFrame有多层索引怎样筛选数据?

In [7]:

import pandas as pd 
%matplotlib inline

In [8]:

fpath = "./pandas-learn-code/datas/stocks/互联网公司股票.xlsx"
stocks = pd.read_excel(fpath)

In [10]:

stocks.shape

Out[10]:

(12, 8)

In [5]:

stocks.head()

Out[5]:

日期 公司 收盘 开盘 交易量 涨跌幅
0 2019-10-03 BIDU 104.32 102.35 104.73 101.15 2.24 0.02
1 2019-10-02 BIDU 102.62 100.85 103.24 99.50 2.69 0.01
2 2019-10-01 BIDU 102.00 102.80 103.26 101.00 1.78 -0.01
3 2019-10-03 BABA 169.48 166.65 170.18 165.00 10.39 0.02
4 2019-10-02 BABA 165.77 162.82 166.88 161.90 11.60 0.00

In [12]:

stocks["公司"].unique()

Out[12]:

array(['BIDU', 'BABA', 'IQ', 'JD'], dtype=object)

In [14]:

# 按公司分组查询收盘价的平均值
stocks.groupby("公司")["收盘"].mean()

Out[14]:

公司
BABA    166.80
BIDU    102.98
IQ       15.90
JD       28.35
Name: 收盘, dtype: float64

1、Series的分层索引MultiIndex

In [16]:

# ser是Series,有两列索引
ser = stocks.groupby(["公司", "日期"])["收盘"].mean()
ser

Out[16]:

公司    日期        
BABA  2019-10-01    165.15
      2019-10-02    165.77
      2019-10-03    169.48
BIDU  2019-10-01    102.00
      2019-10-02    102.62
      2019-10-03    104.32
IQ    2019-10-01     15.92
      2019-10-02     15.72
      2019-10-03     16.06
JD    2019-10-01     28.19
      2019-10-02     28.06
      2019-10-03     28.80
Name: 收盘, dtype: float64

多维索引中,空白的意思是:使用上面的值

In [20]:

ser.index

Out[20]:

MultiIndex(levels=[['BABA', 'BIDU', 'IQ', 'JD'], ['2019-10-01', '2019-10-02', '2019-10-03']],
           codes=[[0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]],
           names=['公司', '日期'])

In [21]:

# unstack把二级索引变成列
# 公司继续作为索引,但日期变为columns
ser.unstack()

Out[21]:

日期 2019-10-01 2019-10-02 2019-10-03
公司
BABA 165.15 165.77 169.48
BIDU 102.00 102.62 104.32
IQ 15.92 15.72 16.06
JD 28.19 28.06 28.80

In [22]:

ser

Out[22]:

公司    日期        
BABA  2019-10-01    165.15
      2019-10-02    165.77
      2019-10-03    169.48
BIDU  2019-10-01    102.00
      2019-10-02    102.62
      2019-10-03    104.32
IQ    2019-10-01     15.92
      2019-10-02     15.72
      2019-10-03     16.06
JD    2019-10-01     28.19
      2019-10-02     28.06
      2019-10-03     28.80
Name: 收盘, dtype: float64

In [24]:

# 将两层索引(公司,日期)都变成了columns
ser.reset_index()

Out[24]:

公司 日期 收盘
0 BABA 2019-10-01 165.15
1 BABA 2019-10-02 165.77
2 BABA 2019-10-03 169.48
3 BIDU 2019-10-01 102.00
4 BIDU 2019-10-02 102.62
5 BIDU 2019-10-03 104.32
6 IQ 2019-10-01 15.92
7 IQ 2019-10-02 15.72
8 IQ 2019-10-03 16.06
9 JD 2019-10-01 28.19
10 JD 2019-10-02 28.06
11 JD 2019-10-03 28.80

2、Series有多层索引MultiIndex怎么筛选数据?

In [25]:

ser

Out[25]:

公司    日期        
BABA  2019-10-01    165.15
      2019-10-02    165.77
      2019-10-03    169.48
BIDU  2019-10-01    102.00
      2019-10-02    102.62
      2019-10-03    104.32
IQ    2019-10-01     15.92
      2019-10-02     15.72
      2019-10-03     16.06
JD    2019-10-01     28.19
      2019-10-02     28.06
      2019-10-03     28.80
Name: 收盘, dtype: float64

In [27]:

ser.loc["BIDU"]

Out[27]:

日期
2019-10-01    102.00
2019-10-02    102.62
2019-10-03    104.32
Name: 收盘, dtype: float64

In [ ]:

# 多层索引,可以用元组的形式筛选

In [28]:

ser.loc[("BIDU","2019-10-02")]

Out[28]:

102.62

In [29]:

ser.loc[:, "2019-10-02"]

Out[29]:

公司
BABA    165.77
BIDU    102.62
IQ       15.72
JD       28.06
Name: 收盘, dtype: float64

3、DataFrame的多层索引MultiIndex

In [30]:

stocks.head()

Out[30]:

日期 公司 收盘 开盘 交易量 涨跌幅
0 2019-10-03 BIDU 104.32 102.35 104.73 101.15 2.24 0.02
1 2019-10-02 BIDU 102.62 100.85 103.24 99.50 2.69 0.01
2 2019-10-01 BIDU 102.00 102.80 103.26 101.00 1.78 -0.01
3 2019-10-03 BABA 169.48 166.65 170.18 165.00 10.39 0.02
4 2019-10-02 BABA 165.77 162.82 166.88 161.90 11.60 0.00

In [40]:

stocks.set_index(["公司", "日期"], inplace=True)

. . .

In [41]:

stocks.head()

Out[41]:

收盘 开盘 交易量 涨跌幅
公司 日期
BIDU 2019-10-03 104.32 102.35 104.73 101.15 2.24 0.02
2019-10-02 102.62 100.85 103.24 99.50 2.69 0.01
2019-10-01 102.00 102.80 103.26 101.00 1.78 -0.01
BABA 2019-10-03 169.48 166.65 170.18 165.00 10.39 0.02
2019-10-02 165.77 162.82 166.88 161.90 11.60 0.00

In [42]:

stocks.index

Out[42]:

MultiIndex(levels=[['BABA', 'BIDU', 'IQ', 'JD'], ['2019-10-01', '2019-10-02', '2019-10-03']],
           codes=[[1, 1, 1, 0, 0, 0, 2, 2, 2, 3, 3, 3], [2, 1, 0, 2, 1, 0, 2, 1, 0, 2, 1, 0]],
           names=['公司', '日期'])

In [43]:

stocks.sort_index(inplace=True)

In [44]:

stocks

Out[44]:

收盘 开盘 交易量 涨跌幅
公司 日期
BABA 2019-10-01 165.15 168.01 168.23 163.64 14.19 -0.01
2019-10-02 165.77 162.82 166.88 161.90 11.60 0.00
2019-10-03 169.48 166.65 170.18 165.00 10.39 0.02
BIDU 2019-10-01 102.00 102.80 103.26 101.00 1.78 -0.01
2019-10-02 102.62 100.85 103.24 99.50 2.69 0.01
2019-10-03 104.32 102.35 104.73 101.15 2.24 0.02
IQ 2019-10-01 15.92 16.14 16.22 15.50 11.65 -0.01
2019-10-02 15.72 15.85 15.87 15.12 8.10 -0.01
2019-10-03 16.06 15.71 16.38 15.32 10.08 0.02
JD 2019-10-01 28.19 28.22 28.57 27.97 10.64 0.00
2019-10-02 28.06 28.00 28.22 27.53 9.53 0.00
2019-10-03 28.80 28.11 28.97 27.82 8.77 0.03

4、DataFrame有多层索引MultiIndex怎样筛选?

【*重要知识*】在选择数据时:

  • 元组(key1,key2)代表筛选多层索引,其中key1是索引第一级,key2是第二级,比如key1=JD, key2=2019-10-02
  • 列表[key1,key2]代表同一层的多个KEY,其中key1和key2是并列的同级索引,比如key1=JD, key2=BIDU

In [45]:

stocks.loc["BIDU"]

Out[45]:

收盘 开盘 交易量 涨跌幅
日期
2019-10-01 102.00 102.80 103.26 101.00 1.78 -0.01
2019-10-02 102.62 100.85 103.24 99.50 2.69 0.01
2019-10-03 104.32 102.35 104.73 101.15 2.24 0.02

In [46]:

# BIDU, 2019-10-02当天所有的相关数据
stocks.loc[("BIDU", "2019-10-02"), :]

Out[46]:

收盘     102.62
开盘     100.85
高      103.24
低       99.50
交易量      2.69
涨跌幅      0.01
Name: (BIDU, 2019-10-02), dtype: float64

In [48]:

# 逻辑关系为BIDU的2019-10-02的开盘数据
stocks.loc[("BIDU", "2019-10-02"), "开盘"]

Out[48]:

100.85

In [50]:

# 并列筛选,BIDU和JD为同级关系
stocks.loc[["BIDU", "JD"], :]

Out[50]:

收盘 开盘 交易量 涨跌幅
公司 日期
BIDU 2019-10-01 102.00 102.80 103.26 101.00 1.78 -0.01
2019-10-02 102.62 100.85 103.24 99.50 2.69 0.01
2019-10-03 104.32 102.35 104.73 101.15 2.24 0.02
JD 2019-10-01 28.19 28.22 28.57 27.97 10.64 0.00
2019-10-02 28.06 28.00 28.22 27.53 9.53 0.00
2019-10-03 28.80 28.11 28.97 27.82 8.77 0.03

In [51]:

stocks.loc[(["BIDU", "JD"], "2019-10-03"), :]

Out[51]:

收盘 开盘 交易量 涨跌幅
公司 日期
BIDU 2019-10-03 104.32 102.35 104.73 101.15 2.24 0.02
JD 2019-10-03 28.80 28.11 28.97 27.82 8.77 0.03

In [52]:

stocks.loc[(["BIDU", "JD"], "2019-10-03"), "收盘"]

Out[52]:

公司    日期        
BIDU  2019-10-03    104.32
JD    2019-10-03     28.80
Name: 收盘, dtype: float64

In [54]:

stocks.loc[("BIDU",["2019-10-02", "2019-10-03"]), "收盘"]

Out[54]:

公司    日期        
BIDU  2019-10-02    102.62
      2019-10-03    104.32
Name: 收盘, dtype: float64

In [55]:

# slice(None)代表筛选这一索引的所有内容
stocks.loc[(slice(None), ["2019-10-02", "2019-10-03"]),:]

Out[55]:

收盘 开盘 交易量 涨跌幅
公司 日期
BABA 2019-10-02 165.77 162.82 166.88 161.90 11.60 0.00
2019-10-03 169.48 166.65 170.18 165.00 10.39 0.02
BIDU 2019-10-02 102.62 100.85 103.24 99.50 2.69 0.01
2019-10-03 104.32 102.35 104.73 101.15 2.24 0.02
IQ 2019-10-02 15.72 15.85 15.87 15.12 8.10 -0.01
2019-10-03 16.06 15.71 16.38 15.32 10.08 0.02
JD 2019-10-02 28.06 28.00 28.22 27.53 9.53 0.00
2019-10-03 28.80 28.11 28.97 27.82 8.77 0.03

In [56]:

# 将多层索引恢复成列
stocks.reset_index()

Out[56]:

公司 日期 收盘 开盘 交易量 涨跌幅
0 BABA 2019-10-01 165.15 168.01 168.23 163.64 14.19 -0.01
1 BABA 2019-10-02 165.77 162.82 166.88 161.90 11.60 0.00
2 BABA 2019-10-03 169.48 166.65 170.18 165.00 10.39 0.02
3 BIDU 2019-10-01 102.00 102.80 103.26 101.00 1.78 -0.01
4 BIDU 2019-10-02 102.62 100.85 103.24 99.50 2.69 0.01
5 BIDU 2019-10-03 104.32 102.35 104.73 101.15 2.24 0.02
6 IQ 2019-10-01 15.92 16.14 16.22 15.50 11.65 -0.01
7 IQ 2019-10-02 15.72 15.85 15.87 15.12 8.10 -0.01
8 IQ 2019-10-03 16.06 15.71 16.38 15.32 10.08 0.02
9 JD 2019-10-01 28.19 28.22 28.57 27.97 10.64 0.00
10 JD 2019-10-02 28.06 28.00 28.22 27.53 9.53 0.00
11 JD 2019-10-03 28.80 28.11 28.97 27.82 8.77 0.03

十七、Pandas的数据转换函数map、apply、applymap

数据转换函数对比:map、apply、applymap:

  1. map:只用于Series,实现每个值->值的映射;
  2. apply:用于Series实现每个值的处理,用于Dataframe实现某个轴的Series的处理;
  3. applymap:只能用于DataFrame,用于处理该DataFrame的每个元素;

1. map用于Series值的转换

实例:将股票代码英文转换成中文名字

Series.map(dict) or Series.map(function)均可

In [2]:

import pandas as pd
stocks = pd.read_excel(r"D:\WinterIsComing\python\New_Wave\pandas_basic\pandas-learn-code\datas\stocks\互联网公司股票.xlsx")

In [3]:

stocks.head()

Out[3]:

日期 公司 收盘 开盘 交易量 涨跌幅
0 2019-10-03 BIDU 104.32 102.35 104.73 101.15 2.24 0.02
1 2019-10-02 BIDU 102.62 100.85 103.24 99.50 2.69 0.01
2 2019-10-01 BIDU 102.00 102.80 103.26 101.00 1.78 -0.01
3 2019-10-03 BABA 169.48 166.65 170.18 165.00 10.39 0.02
4 2019-10-02 BABA 165.77 162.82 166.88 161.90 11.60 0.00

In [7]:

stocks["公司"].unique()

Out[7]:

array(['BIDU', 'BABA', 'IQ', 'JD'], dtype=object)

In [8]:

# 公司股票代码到中文的映射,注意这里是小写
dict_company_names={
    "bidu":"百度",
    "baba":"阿里巴巴",
    "iq":"爱奇艺",
    "jd":"京东"
}

方法1:Series.map(dict)

In [9]:

stocks["中文公司1"]=stocks["公司"].str.lower().map(dict_company_names)

In [10]:

stocks

Out[10]:

日期 公司 收盘 开盘 交易量 涨跌幅 中文公司1
0 2019-10-03 BIDU 104.32 102.35 104.73 101.15 2.24 0.02 百度
1 2019-10-02 BIDU 102.62 100.85 103.24 99.50 2.69 0.01 百度
2 2019-10-01 BIDU 102.00 102.80 103.26 101.00 1.78 -0.01 百度
3 2019-10-03 BABA 169.48 166.65 170.18 165.00 10.39 0.02 阿里巴巴
4 2019-10-02 BABA 165.77 162.82 166.88 161.90 11.60 0.00 阿里巴巴
5 2019-10-01 BABA 165.15 168.01 168.23 163.64 14.19 -0.01 阿里巴巴
6 2019-10-03 IQ 16.06 15.71 16.38 15.32 10.08 0.02 爱奇艺
7 2019-10-02 IQ 15.72 15.85 15.87 15.12 8.10 -0.01 爱奇艺
8 2019-10-01 IQ 15.92 16.14 16.22 15.50 11.65 -0.01 爱奇艺
9 2019-10-03 JD 28.80 28.11 28.97 27.82 8.77 0.03 京东
10 2019-10-02 JD 28.06 28.00 28.22 27.53 9.53 0.00 京东
11 2019-10-01 JD 28.19 28.22 28.57 27.97 10.64 0.00 京东

方法2:Series.map(function)

function的参数是Series的每个元素的值

In [13]:

# lambda x中的x代表Series的每个值(即stocks["公司"]中的每个值)
stocks["公司中文2"]=stocks["公司"].map(lambda x : dict_company_names[x.lower()])

In [12]:

stocks.head()

Out[12]:

日期 公司 收盘 开盘 交易量 涨跌幅 中文公司1 公司中文2
0 2019-10-03 BIDU 104.32 102.35 104.73 101.15 2.24 0.02 百度 百度
1 2019-10-02 BIDU 102.62 100.85 103.24 99.50 2.69 0.01 百度 百度
2 2019-10-01 BIDU 102.00 102.80 103.26 101.00 1.78 -0.01 百度 百度
3 2019-10-03 BABA 169.48 166.65 170.18 165.00 10.39 0.02 阿里巴巴 阿里巴巴
4 2019-10-02 BABA 165.77 162.82 166.88 161.90 11.60 0.00 阿里巴巴 阿里巴巴

2. apply用于Series和DataFrame的转换

  • Series.apply(function), 函数的参数是每个值
  • DataFrame.apply(function), 函数的参数是Series

Series.apply(function)

function的参数是Series的每个值

In [14]:

stocks["中文公司3"]=stocks["公司"].apply(lambda x : dict_company_names[x.lower()])

In [16]:

stocks.head()

Out[16]:

日期 公司 收盘 开盘 交易量 涨跌幅 中文公司1 公司中文2 中文公司3
0 2019-10-03 BIDU 104.32 102.35 104.73 101.15 2.24 0.02 百度 百度 百度
1 2019-10-02 BIDU 102.62 100.85 103.24 99.50 2.69 0.01 百度 百度 百度
2 2019-10-01 BIDU 102.00 102.80 103.26 101.00 1.78 -0.01 百度 百度 百度
3 2019-10-03 BABA 169.48 166.65 170.18 165.00 10.39 0.02 阿里巴巴 阿里巴巴 阿里巴巴
4 2019-10-02 BABA 165.77 162.82 166.88 161.90 11.60 0.00 阿里巴巴 阿里巴巴 阿里巴巴

DataFrame.apply(function)

function的参数是对应轴的Series

In [18]:

stocks["中文公司4"]=stocks.apply(lambda x: dict_company_names[x["公司"].lower()], axis=1)

In [19]:

stocks["公司"]

Out[19]:

0     BIDU
1     BIDU
2     BIDU
3     BABA
4     BABA
5     BABA
6       IQ
7       IQ
8       IQ
9       JD
10      JD
11      JD
Name: 公司, dtype: object

注意这个代码:
1、apply是在stocks这个DataFrame上调用;
2、lambda x的x是一个Series,因为指定了axis=1所以Seires的key是列名,可以用x[‘公司’]获取

In [20]:

stocks.head()

Out[20]:

日期 公司 收盘 开盘 交易量 涨跌幅 中文公司1 公司中文2 中文公司3 中文公司4
0 2019-10-03 BIDU 104.32 102.35 104.73 101.15 2.24 0.02 百度 百度 百度 百度
1 2019-10-02 BIDU 102.62 100.85 103.24 99.50 2.69 0.01 百度 百度 百度 百度
2 2019-10-01 BIDU 102.00 102.80 103.26 101.00 1.78 -0.01 百度 百度 百度 百度
3 2019-10-03 BABA 169.48 166.65 170.18 165.00 10.39 0.02 阿里巴巴 阿里巴巴 阿里巴巴 阿里巴巴
4 2019-10-02 BABA 165.77 162.82 166.88 161.90 11.60 0.00 阿里巴巴 阿里巴巴 阿里巴巴 阿里巴巴

3. applymap用于DataFrame所有值的转换

In [21]:

sub_df = stocks[["收盘","开盘","高","低","交易量"]]

In [22]:

sub_df

Out[22]:

收盘 开盘 交易量
0 104.32 102.35 104.73 101.15 2.24
1 102.62 100.85 103.24 99.50 2.69
2 102.00 102.80 103.26 101.00 1.78
3 169.48 166.65 170.18 165.00 10.39
4 165.77 162.82 166.88 161.90 11.60
5 165.15 168.01 168.23 163.64 14.19
6 16.06 15.71 16.38 15.32 10.08
7 15.72 15.85 15.87 15.12 8.10
8 15.92 16.14 16.22 15.50 11.65
9 28.80 28.11 28.97 27.82 8.77
10 28.06 28.00 28.22 27.53 9.53
11 28.19 28.22 28.57 27.97 10.64

In [23]:

# 将这些数字取整数,应用于所有元素(即表格中所有的值)
sub_df.applymap(lambda x: int(x))

Out[23]:

收盘 开盘 交易量
0 104 102 104 101 2
1 102 100 103 99 2
2 102 102 103 101 1
3 169 166 170 165 10
4 165 162 166 161 11
5 165 168 168 163 14
6 16 15 16 15 10
7 15 15 15 15 8
8 15 16 16 15 11
9 28 28 28 27 8
10 28 28 28 27 9
11 28 28 28 27 10

In [25]:

# 直接修改原df的这几列
stocks.loc[:, ["收盘","开盘","高","低","交易量"]] = sub_df.applymap(lambda x: int(x))

In [26]:

stocks.head()

Out[26]:

日期 公司 收盘 开盘 交易量 涨跌幅 中文公司1 公司中文2 中文公司3 中文公司4
0 2019-10-03 BIDU 104 102 104 101 2 0.02 百度 百度 百度 百度
1 2019-10-02 BIDU 102 100 103 99 2 0.01 百度 百度 百度 百度
2 2019-10-01 BIDU 102 102 103 101 1 -0.01 百度 百度 百度 百度
3 2019-10-03 BABA 169 166 170 165 10 0.02 阿里巴巴 阿里巴巴 阿里巴巴 阿里巴巴
4 2019-10-02 BABA 165 162 166 161 11 0.00 阿里巴巴 阿里巴巴 阿里巴巴 阿里巴巴

十八、Pandas怎样对每个分组应用apply函数?

知识:Pandas的GroupBy遵从split、apply、combine模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lUn7YSnp-1597761927713)(http://localhost:8891/notebooks/pandas-learn-code/other_files/pandas-split-apply-combine.png)]

这里的split指的是pandas的groupby,我们自己实现apply函数,apply返回的结果由pandas进行combine得到结果

GroupBy.apply(function)

  • function的第一个参数是dataframe
  • function的返回结果,可是dataframe、series、单个值,甚至和输入dataframe完全没关系

本次实例演示:

  1. 怎样对数值列按分组的归一化?
  2. 怎样取每个分组的TOPN数据?

实例1:怎样对数值列按分组的归一化?

将不同范围的数值列进行归一化,映射到[0,1]区间:

  • 更容易做数据横向对比,比如价格字段是几百到几千,增幅字段是0到100
  • 机器学习模型学的更快性能更好

归一化的公式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CuwTEdIl-1597761927714)(http://localhost:8891/notebooks/pandas-learn-code/other_files/Normalization-Formula.jpg)]

演示:用户对电影评分的归一化

每个用户的评分不同,有的乐观派评分高,有的悲观派评分低,按用户做归一化

In [1]:

import pandas as pd

In [7]:

ratings = pd.read_csv(
    "./pandas-learn-code/datas/movielens-1m/ratings.dat",
    sep="::",
    engine="python",
    names="UserID::MovieID::Rating::Timestamp".split("::")
)

In [8]:

ratings.head()

Out[8]:

UserID MovieID Rating Timestamp
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291

In [10]:

# 实现按照用户ID分组,然后对ratings进行归一化
def ratings_norm(df):
# 实际参数是每个用户分组的df(按照UserID分组的DataFrame)
    max_value = df["Rating"].max()
    min_value = df["Rating"].min()
    df["Rating_norm"] = df["Rating"].apply(lambda x:(x - min_value)/(max_value - min_value))
    return df

# 按照用户分组,apply一个函数,给该DataFrame新增了一列,实现了Rating列的归一化
ratings = ratings.groupby("UserID").apply(ratings_norm)

In [12]:

ratings["Rating"]

. . .

In [16]:

type(ratings)

Out[16]:

pandas.core.frame.DataFrame

In [17]:

ratings[ratings["UserID"]==1].head()

Out[17]:

UserID MovieID Rating Timestamp Rating_norm
0 1 1193 5 978300760 1.0
1 1 661 3 978302109 0.0
2 1 914 3 978301968 0.0
3 1 3408 4 978300275 0.5
4 1 2355 5 978824291 1.0

实例2:怎么取每个分组的TOP N数据

获取2018年每个月温度最高的2天数据

In [18]:

fpath = "./pandas-learn-code/datas/beijing_tianqi/beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)

In [19]:

df.head()

Out[19]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel
0 2018-01-01 3℃ -6℃ 晴~多云 东北风 1-2级 59 2
1 2018-01-02 2℃ -5℃ 阴~多云 东北风 1-2级 49 1
2 2018-01-03 2℃ -5℃ 多云 北风 1-2级 28 1
3 2018-01-04 0℃ -8℃ 东北风 1-2级 28 1
4 2018-01-05 3℃ -6℃ 多云~晴 西北风 1-2级 50 1

In [21]:

# 替换掉温度后的℃
df.loc[:, "bWendu"]=df["bWendu"].str.replace("℃","").astype("int32")
df.loc[:, "yWendu"]=df["yWendu"].str.replace("℃","").astype("int32")

In [22]:

# 新增一列为月份
df["month"] = df["ymd"].str[0:7]
df.head()

Out[22]:

ymd bWendu yWendu tianqi fengxiang fengli aqi aqiInfo aqiLevel month
0 2018-01-01 3 -6 晴~多云 东北风 1-2级 59 2 2018-01
1 2018-01-02 2 -5 阴~多云 东北风 1-2级 49 1 2018-01
2 2018-01-03 2 -5 多云 北风 1-2级 28 1 2018-01
3 2018-01-04 0 -8 东北风 1-2级 28 1 2018-01
4 2018-01-05 3 -6 多云~晴 西北风 1-2级 50 1 2018-01

In [24]:

def getWenduTopN(df, topn):
    # 这里的df,是每个月份分组group的df
    return df.sort_values(by="bWendu")[["ymd", "bWendu"]][-topn:]

df.groupby("month").apply(getWenduTopN, topn=2).head()

Out[24]:

ymd bWendu
month
2018-01 13 2018-01-14 6
18 2018-01-19 7
2018-02 53 2018-02-23 10
56 2018-02-26 12
2018-03 86 2018-03-28 25

In [25]:

df[["ymd","bWendu"]]

. . .

我们看到,groupby的apply函数返回的DataFrame,其实和原来的DataFrame其实可以完全不一样

你可能感兴趣的:(Pandas基础操作(上))