一、读取数据

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

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

导入模块

import pandas as pd

1. 读取纯文本文件

pd.read_csv(数据文件名, sep = '', header = 'infer', names = None, index_col = None, dtype = None, engine = None, nrows = None)

常用参数

参数名称 说明
filepath 接收string。代表文件路径,无默认。该字符串可以是一个URL。有效的URL方案包括http,ftp,s3和file
sep 接收string。代表分隔符。默认为','
header 接收int或sequence。表示将某行数据作为列名,默认为infer,表示自动识别
names 接收array。表示列名,默认为None
index_col 接收int、sequence或False。表示索引列的位置,取值为sequence则代表多重索引,默认为None
dtype 接收dict。代表写入的数据类型(列名为key,数据格式为values)。默认为None
engine 接收c或者Python。代表数据解析引擎,默认为c
nrows 接收int。表示读取前n行。默认为None
encoding 取值为'utf-8'、'utf-16'、'gbk'、'gb2312'、'gb18030'等。代表文件的编码格式,如果指定错误,数据则无法读取

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

fpath = '文件路径.csv'

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

# 查看前5行数据
data.head() # ()内可以填写数字来调整显示的行数,默认为5

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

# 查看列名列表
data.columns

# 查看索引列
data.index

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


② 读取txt文件,自己指定分隔符、列名
fpath = '文件路径.txt'

data =  pd.read_csv(fpath
                    ,sep = '指定的分隔符',
                    header = None, # 无表头时一定要加上
                    names = [指定列名的列表])

2. 读取xlsx格式excel文件

pd.read_excel(数据文件名, sheefname = 0, header = 0, names = None, index_col = None, dtype = None)

重要参数

参数名称 说明
io 接收string。代表文件路径,无默认。
sheefname 接收string、int。代表excel表内数据的分表位置,默认为0
header 接收int或sequence。表示将某行数据作为列名,默认为infer,表示自动识别
names 接收array。表示列名,默认为None
index_col 接收int、sequence或False。表示索引列的位置,取值为sequence则代表多重索引,默认为None
dtype 接收dict。代表写入的数据类型(列名为key,数据格式为values)。默认为None
fpath = '文件路径.xlsx'

data =  pd.read_excel(fpath)

3. 读取mysql数据表

pd.read_sql(sql,con,index_col = None, coerce_float = True, columns = None)

重要参数

参数名称 说明
sql string类型。代表SQL语句。
con 表示数据库的连接信息
index_col 接收int、sequence或False。表示索引列的位置,取值为sequence则代表多重索引,默认为None
coerce_float boolean。将数据库中的decimal类型的数据转换成pandas中的float64数据类型,默认True
columns list类型,表示读取数据的列名,默认为None
import pymysql

# 数据库连接信息
conn = pymysql.connect(host ='地址代码',
                       uesr = '用户名',
                       password = '密码',
                       database = '数据库名',
                       charset = '编码格式'
)

data = pd.read_sql('sql语句', con = conn)

二、Pandas的数据结构DataFrame和Series

import pandas as pd
import numpy as np

1. Series

  • 一维数据,一行或一列
  • 一种类似于一维数组的对象,有一组数据(不同数据类型)以及一组与之相关的数据标签(索引)组成

① 通过列表创建Series

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

# 获取索引
s1.index

# 获取数据
s1.values

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

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

s2.index

③ 使用Python字典创建Series

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

s3 = pd.Series(sdict)

④ 根据标签索引查询数据

# 获取单个值及它的类型
s2['a']
type(s2['a'])

# 获取多个值及返回值类型
s2[['b','a']]
type(s2[['b','a']])

2. DataFrame

二维数据,整个表格,多行多列,为表格型的数据结构:

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

    创建dataframe最常用的方法是文件导入,见第一部分。

① 根据多个字典序列创建dataframe

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

df.dtypes

df.index

df.columns

3.从DataFrame中查询出Series

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

① 查询一列,结果是pd.Series 对象

df['year']
type(df['year'])

② 查询多列,结果是pd.DataFrame 对象

df[['year','pop']]
type(df[['year','pop']])

③ 查询一行,结果是pd.Series 对象

df.loc[1]
type(df.loc[1])

④ 查询多行,结果是pd.DataFrame 对象

# loc切片包含末尾元素
df.loc[1:3]
type(df.loc[1:3])

三、查询数据的5种方法

  1. df.loc方法(推荐)

根据行、列的标签值查询
既能查询,又能覆盖写入,较为推荐

① 使用单个label值查询数据

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

# 得到单个值
df.loc[行索引值, 列索引值]

# 得到一个Series
df.loc[行索引值, [列索引列表]]

② 使用值列表批量查询

# 得到Series
df.loc[[行索引列表], 列索引]

# 得到DataFrame
df.loc[[行索引列表], [列索引列表]]

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

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

# 行index按区间
df.loc[起始行索引:终止行索引, 列索引]

# 列columns按区间
df.loc[行索引,起始列索引:终止列索引]

# 行和列都按区间
df.loc[起始行索引:终止行索引,起始列索引:终止列索引]

④ 使用条件表达式查询

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

# 简单条件查询
df.loc[df.[列名]>某值, :] 
# df.[列名]>某值-----------条件

# 复杂条件查询
# *注意*:组合条件用&符号合并,每个条件判断都得带括号
df.loc[(df.[列名1]>某值) & (df.[列名2]>某值) &……, :] 
# df.loc[(df.[列名1]>某值) & (df.[列名2]>某值) &……--------条件

⑤ 调用函数查询

# 直接写lambda表达式
df.loc[lambda df: ((df.[列名1]>某值) & (df.[列名2]>某值)), :]

# 编写自定义函数
def 函数名(参数):
    函数体
    
df.loc[函数名, :] # 这里函数没有括号,函数式编程的本质:函数本身可以像变量一样传递

注意

  • 以上查询方法,既适用于行,也适用于列
  • 注意观察降维DataFrame>Series>值
  1. df.iloc方法

根据行、列的数字位置查询

  1. df.where方法
  2. df.query方法

四、新增数据列

在进行数据分析时,经常需要按照一定条件创建新的数据列,然后进行进一步分析

1. 直接赋值

# 如果列名存在,则修改该列值,不存在则新增一列
df.loc[:, '列名'] = Series对象

2. df.apply方法

  • 对DataFrame对象沿某个轴,应用一个函数
  • 传给apply函数的对象是Series类型,它的索引要么对应DataFrame对象的index(axis = 0),要么对应DataFrame对象的columns(axis = 1)
  • 返回对象是Series类型
# 自定义函数
def 函数名(参数):
    函数体
    
# 设置axis=1,这里的series的index是columns
df.loc[:, '新列名']= df.apply(函数名,axis = 1) # 这里函数名位置也可以是匿名函数

3. df.assign方法

  • 与apply方法类似,但可以同时添加多个新的列
  • 直接将Series的index对应DataFrame对象的columns,所以不用添加axis参数
  • 返回对象为原对象+新增的一列,但不会修改原对象
df.assign(新列名1 = 自定义函数名1或匿名函数1,
         新列名2 = 自定义函数名2或匿名函数2)

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

按条件选择数据,然后对这部分数据赋值新列

# 先创建空列
df['新列名'] = ''

df.loc[表达式>值, '新列名'] = 值1
df.loc[表达式<=值, '新列名'] = 值2

五、数据统计函数

1. 汇总类统计

① 一下子提取所有数字列的统计结果

df.describe()

② 查看单个Series的数据

# 平均值
df['列名'].mean()

# 最大值
df['列名'].max()

# 最小值
df['列名'].min()

2. 唯一去重和按值计数

① 唯一性去重

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

df['列名'].unique()

② 按值计数

df['列名'].value_counts()

3. 相关系数和协方差

用途(举例):

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

相关概念:

  • 协方差:衡量同向反向程度,如果协方差为正,说明X、Y同向变化,协方差越大说明同向程度越高;如果协方差为负,说明X、Y反向运动,协方差越小说明反向程度越高
  • 相关系数:衡量相似度程度,当他们的相关系数为1时,说明两个变量变化时的正向相似度最大,当相关系数为-1时,说明两个变量变化时的反向相似度最大
# 协方差矩阵
df.cov()

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

# 单独查看两列的相关系数
df['列名1'].corr(df['列名2'])

六、缺失值的处理

1. 检测是否为空值----isnull 和 notnull

可用于DataFrame和Series

# 检查全表是否为空值
df.isnull()

# 检查某列是否为空值
df['列名'].isnull()

# 检查某列是否不为空值
df['列名'].notnull()

# 筛选没有空值的所有行
df.loc[df['列名'].notnull(),:]

2. 丢弃、删除缺失值----dropna

  • axis:删除行还是列,取值:{0 or 'index', 1 or 'columns'}, 默认值为0
  • how:如果等于any,则任何值为空都删除;如果等于all,则所有值都为空才删除
  • inplace:如果为True则修改当前df,否则返回新的df
# 删除全是空值的列
df.dropna(axis = 'columns', how = 'all', inplace = True)

# 删除全是空值的行
df.dropna(axis = 'index', how = 'all', inplace = True)

3. 填充空值----fillna

  • value:用于填充的值,可以是单个值,或者字典(key是列名,value是值)
  • method:等于ffill使用前一个不为空的值填充(forward fill);等于bfill使用后一个不为空的值填充(backward fill)
  • axis:按行填充还是列填充,取值:{0 or 'index', 1 or 'columns'}
  • inplace:如果为True则修改当前df,否则返回新的df
# 将某列为空的填充为0
df.fillna({'列名': 0})
# 等价于
df.loc[:,'列名'] = df['列名'].fillna(0) 

# 使用前一个值填充空值
df.loc[:,'列名'] = df['列名'].fillna(method = 'ffill') 

七、数据排序

1. Series的排序

Series.sort_values(ascending = True, inplace = False)

参数说明:

参数 说明
ascending 默认为True升序排序,为False降序排序
inplace 是否修改原始Series

2. DataFrame的排序

DataFrame.sort_values(by = , ascending = True, inplace = False)

参数说明:

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

八、Pandas字符串处理

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

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

# 字符串替换函数
df['列名'].str.replace('原字符串', '替换字符串')

# 判断是否为数字
df['列名'].str.isnumeric()

# 获取字符串长度
df['列名'].str.len()

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

condition = df['列名'].str.startswith('字符串')

3. 需要多次str处理的链式操作

例如: 如果有日期形如yyyy-mm-dd,提取形式为yyyymm

# 先替换再截取

# 替换
df['列名'].str.replace('-','') 

# 截取
# * 注意:* 每次调用函数,都会返回一个新Series, 再使用时需要重新调用str属性
df['列名'].str.replace('-','').str.slice(0,6)

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

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

怎样将yyyy年mm月dd日中的年、月、日三个中文字符去除

# 方法一:链式replace
df['列名'].str.replace('年','').str.replace('月','').str.replace('日','')

# 方法二:正则表达式模式(Series.str默认开启了正则表达式模式)
df['列名'].str.replace('[年月日]','')

九、axis参数

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

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

import numpy as np
import pandas as pd

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

# 单列drop,删除某一列
df.drop('A',axis = 1)

# 单行drop,删除某一行
df.drop(1,axis = 0)

# 按axis = 0/index 执行mean聚合操作,输出每列的结果
df.mean(axis = 0)
# 指定了按哪个axis,就是这个axis要动起来(类似遍历),其他axis保持不动

# 按axis = 1/columns 执行mean聚合操作,输出每行的结果
df.mean(axis = 1)

十、index的用途

用途总结:

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

1. 使用index查询数据

# 将dataframe中原始的列作为索引,drop = False,让索引列还保持在column中
df.set_index('列名', inplace = True, drop = False)

# 使用column的condition查询方法
df.loc[df['列名']= 某值]

# 将该列作为索引,然后使用index的查询方法
df.loc[某值]

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

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

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

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

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

# 判断索引是否唯一
df_shuffle.index.is_unique

# 计时,查询索引等于某值的数据性能
%timeit df_shuffle.loc[某值]

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

df_sorted = df_shuffle.sort_index()

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

# 判断索引是否唯一
df_sorted.index.is_unique

# 计时,查询索引等于某值的数据性能
%timeit df_sorted.loc[某值]

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

包括Series和DataFrame

s1 = pd.Series([1, 2, 3], index = list('abc'))
s2 = pd.Series([2, 3, 4], index = list('bcd'))

s1+s2
# 根据索引对齐,进行运算,若有一方缺失则显示NaN

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

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

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

十一、Pandas实现DataFrame的merge(类似Sql的join)

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或者series的key
  • left_index、right_index:使用index而不是普通的column做join
  • suffixes:两个元素的后缀,如果列有重名,自动添加后缀,默认是('_x','_y')

注1: left_on、right_on在两个dataframe的key不一致时,分别指定
注2: left_index、right_index是在使用index做key时使用

1. 电影数据集的join实例

电影评分数据集
数据来源:https://grouplens.org/datasets/movielens/
包含文件:

  • 用户对电影评分数据ratings.dat
  • 用户本身的信息数据users.dat
  • 电影本身的数据movies.dat
import pandas as pd

# 读取ratings.dat数据
df_ratings = pd.read_csv('ratings.dat'
                       , sep = '::'
                       ,engine = 'python'
                       , names = 'UserID::MovieID::Rating::Timestamp'.split('::'))

# 读取users.dat数据
df_users = pd.read_csv('users.dat'
                       , sep = '::'
                       ,engine = 'python'
                       , names = 'UserID::Gender::Age::Occupation::Zip-code'.split('::'))

# 读取movies.dat数据
df_movies = pd.read_csv('movies.dat'
                       , sep = '::'
                       ,engine = 'python'
                       , names = 'MovieID::Title::Genres'.split('::'))

# 先用ratings.dat数据inner join users.dat数据
df_ratings_users = pd.merge(df_ratings 
                            ,df_users
                            , how = 'inner'
                            , left_on = 'UserID'
                            , right_on = 'UserID'
                           )

# 再用join之后的数据继续inner join movies.dat数据
df_ratings_users_movies = pd.merge(df_ratings_users 
                            ,df_movies
                            , how = 'inner'
                            , left_on = 'MovieID'
                            , right_on = 'MovieID'
                           )

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

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

①. one-to-one一对一关系的merge

left = pd.DataFrame('sno': [11, 12, 13, 14],
                   'name': ['name_a','name_b','name_c','name_d'])
right =  pd.DataFrame('sno': [11, 12, 13, 14],
                     'name': ['21', '22', '23', '24'])
                     
pd.merge(left, right, on = 'sno')    
# 一对一的关系,结果有四条

②. one-to-many一对多关系的merge

left = pd.DataFrame('sno': [11, 12, 13, 14],
                   'name': ['name_a','name_b','name_c','name_d'])
right =  pd.DataFrame('sno': [11, 11, 11, 12, 12, 13],
                     'grade': ['语文88', '数学90', '英语75', '语文66', '数学55', '英语29']) 
                     
pd.merge(left, right, on = 'sno')
# 以数目多的一方为准

③. many-to-many多对多关系的merge

left = pd.DataFrame('sno': [11, 11, 12, 12, 12],
                   '爱好': ['篮球','羽毛球','乒乓球','篮球', '足球'])
right =  pd.DataFrame('sno': [11, 11, 11, 12, 12, 13],
                     'grade': ['语文88', '数学90', '英语75', '语文66', '数学55', '英语29']) 
                     
pd.merge(left, right, on = 'sno')
# 结果数量会出现乘法

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

① inner join,默认

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

② left join

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

③ right join

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

④ outer join

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

4. 如果出现非key的字段重名怎么办

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']})

pd.merge(left, right, on = 'key') # 默认为_x,_y后缀区分

pd.merge(left, right, on = 'key', suffixes = ('_left', '_right')) # 自定义后缀区分

十二、数据的合并concat

1. 使用场景:

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

2. concat语法说明:

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

3. concat语法

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

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

4. append语法

append只有按行合并,没有按列合并,相当于concat按行的简写形式 pd.append(other, ignore_index = False)

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

代码演示:

import pandas as pd

# 1.使用pd.concat合并数据
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']})
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']})

# ① 默认的concat,参数为axis=0,join = outer,ignore_index =False
df.concat([df1,df2])

# ② 使用ignore_index =True可以忽略原来的索引
df.concat([df1,df2], ignore =True)

# ③ 使用join = 'inner'可以过滤掉不匹配的列
df.concat([df1,df2], ignore =True, join ='inner')

# ④ 使用axis=1相当于添加新列
#     a. 添加一列Series
s1 = pd.Series(list(range(4)),name = 'F')
pd.concat([df1,s1], axis = 1)
#     b. 添加多列Series
s2 = df1.apply(lambda x: x['A']+'_GG', axis = 1)
s2.name = 'G'
pd.concat([df1,s1,s2], axis = 1)
#         列表可以只有Series
pd.concat([s1,s2],axis =1)
#         列表可以混合顺序
pd.concat([s1,df1,s2], axis = 1)




# 2. 使用df.append按行合并数据
df1 = pd.DataFrame([[1,2],[3,4]],columns = list('AB'))
df2 = pd.DataFrame([[5,6],[7,8]],columns = list('AB'))

# ① 给一个dataframe添加另一个datafrme
df1.append(df2)

# ② 忽略原来的索引ignore_index=True
df1.append(df2,ignore_index=True)

# ③ 可以一行一行的给DataFrame添加数据
df = pd.DataFrame(columns = 'A')
#      a.低性能版本
for i in range(5):
    # 这里每次都在复制
    df=df.append({'A':i},ignore_index =True)
#      b.性能好的版本
#        第一个入参的是一个列表,避免了多次复制
pd.concat([pd.DataFrame([i],columns ='A') for i in range(5)],
          ignore_index =True)

十三、批量拆分与合并excel

1. 将一个大Excel等份拆成多个Excel

步骤:
① 使用df.iloc方法,将一个大的DataFrame,拆分成多个小DataFrame
② 将使用dataframe.to_excel保存每个小Excel

# 数据目录
work_dir = '目录路径'

# 拆分后存放的目录
splits_dir = f'{work_dir}/split'

# 如果目录不存在,则创建该目录
import os
if not ps.path.exist(splits_dir):
    os.mkdir(splits_dir)

import pandas as pd

# 读取大Excel文件
df_source = pd.read_excel('文件路径')

# 查看数据大小
df_source.shape

# 将数据的行数赋值给变量
total_row_count = df_source.shape[0]


# 1. 计算拆分后的每个excel的行数
# 这个大的excel,会拆分给这几个人
user_names = ['xiao_shuai','xiao_wang','xiao_ming','xiao_lei','xiao_bo','xiao_hong']
# 每个人的任务数目
split_size = total_row_count//len(user_names)
if total_row_count%len(user_names) != 0:
    split_size += 1

# 2. 拆分成多个DataFrame
df_subs = [] # 创建空列表用于保存子集

for idx,user_name in enumerate(users_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))

# 3. 将每个dataframe存入excel
for idx,user_name,df_sub in df_subs:
    df_sub.to_excel(f'{splits_dir}/excel_split_{idx}_{user_name}.xlsx', index = False)

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

步骤:
① 遍历文件夹,得到要合并的Excel文件列表
② 分别读取到dataframe,给每个df添加一列用于标记来源
③ 使用pd.concat进行df批量合并
④ 将合并后的dataframe输出到excel

# 1. 遍历文件夹,得到要合并的Excel名称列表
import os 
# 创建一个空列表用于保存所有小excel的文件名称
excel_names = []
for excel_name in os.listdir('splits_dir'): # listdir('保存所有小excel文件的文件夹路径')
    excel_names.append(excel_name)

# 2. 分别读取到dataframe
# 创建一个空列表用于保存所有的dataframe
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('excel_split_','').replace('.xlsx','')[2:]
    # 添加新列,用户名称
    df_split['username'] = username
    df_list.append(df_split)

# 3. 使用pd.concat进行合并
df_concat  =pd.concat(df_list)

# 4. 将合并后的dataframe输出到excel
df_concat.to_excel('文件路径及文件名', index = False)

十四、groupby分组数据统计

类似SQL:
select city, max(temperature) from city_weather group by city; groupby:先对数据分组,然后在每个分组上应用聚合函数、转换函数

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

import pandas as pd
import numpy as np

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)})

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

df.groupby('A').sum()

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

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

df.groupby(['A','B']).mean()
# ('A','B')成对变成了二级索引

df.groupby(['A','B'], as _index = False).mean() 
# ('A','B')不会变成索引

③ 同时查看多种数据统计

df.groupby('A').agg([np.sum, np.mean, np.std])
# 列变成了多级索引

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

# 方法一:预过滤,性能更好
df.groupby('A')['C'].agg([np.sum, np.mean, np.std])

# 方法二:
df.groupby('A').agg([np.sum, np.mean, np.std])['C']

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

df.groupby('A').agg({'C':np.sum, 'D':np.mean})

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

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

① 遍历单个列聚合的分组

g = df.groupby('A')
for name,group in g:
    print(name)
    print(group)

# 可以获取单个分组的数据
g.get_group('bar')

② 遍历多个列聚合的分组

g = df.groupby(['A','B'])
for name,group in g:
    print(name)
    print(group)
# name是一个2个元素的tuple,代表不同的列

# 获取某个分组
g.get_group(('foo','one'))

# 可以直接查询group后的某几列,生成Series或者子DataFrame
g['C']
for name,group in g['C']:
    print(name)
    print(group)
    print(type(group))             

总结:所有的聚合统计,都是在dataframe和series上进行的

十五、分层索引MultiIndex

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

# 读取数据
stocks = pd.read_excel('互联网公司股票.xlsx')

# 数据大小
stocks.shape

# 取值
stocks['公司'].unique()

# 分组求平均值
stocks.groupby('公司')['收盘'].mean()

1. Series的分层索引MultiIndex

ser = stocks.groupby(['公司', '日期'])['收盘'].mean()
ser
# 多维索引中,空白的意思是:使用上面的值

# 输出MultiIndex索引列表
ser.index

# unstack把二级索引变成列
ser.unstack()

# 将多维索引变为普通列
ser.reset_index()

2. Series有多层索引怎样筛选数据

# 筛选第一级索引
ser.loc['BIDU']

# 多层筛选,用元组的形式筛选
ser.loc[('BIDU','2019-10-02')]

# 筛选第二级索引
ser.loc[:,'2019-10-02']

3. DataFrame的多层索引MultiIndex

# 将df普通列设为多层索引
stocks.set_index(['公司','日期'],inplace = True)
stocks.index

# 根据一级索引、二级索引排序
stocks.sort_index(inplace = True)

4. DataFrame有多层索引怎样筛选数据

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

  • 元组(key1,key2)代表筛选多层索引,其中key1是索引第一级,key2是索引第二级
  • 列表(key1,key2)代表筛选同一层的多个key,其中key1和key2是并列的同级索引
# 筛选一级索引为'BIDU'
stocks.loc['BIDU']

# 筛选多级索引为('BIDU','2019-10-02')
stocks.loc[('BIDU','2019-10-02'),:]

# 筛选多级索引为('BIDU','2019-10-02'),列名为'开盘'
stocks.loc[('BIDU','2019-10-02'),'开盘']

# 筛选一级索引为'BIDU'或'JD'
stocks.loc[['BIDU','JD'],:]

# 筛选一级索引为'BIDU'或'JD',二级索引为'2019-10-02'
stocks.loc[(['BIDU','JD'],'2019-10-02'),:]

# 筛选一级索引为'BIDU'或'JD',二级索引为'2019-10-02',列名为'开盘'
stocks.loc[(['BIDU','JD'],'2019-10-02'),'收盘']

# 筛选一级索引为'BIDU',二级索引为'2019-10-02'或'2019-10-03',列名为'开盘'
stocks.loc[('BIDU',['2019-10-02','2019-10-03']),'收盘']

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

# 将多维索引变为普通列
stocks.reset_index()

十六、map、apply、applymap数据转换函数

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

  • map:只用于Series,实现每个值->值的映射
  • apply:既可用于Series实现每个值的处理,又可用于DataFrame实现某个轴的Series的处理
  • applymap:只能用于DataFrame,用于处理该DataFrame的每个元素

1.map用于Series值的转换

实例:将股票代码英文转换成中文名字
Series.map(dict) or Series.map(function)均可

import pandas as pd

# 导入数据集
stocks = pd.read_excel('互联网公司股票.xlsx')
stocks.head()

# 查看取值
stocks['公司'].unique()

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

# 方法1:Series.map(dict)
stocks['公司中文1'] = stocks['公司'].str.lower().map(dict_company_names)

# 方法2:Series.map(function)
# function的参数是Series的每个元素
stocks['公司中文2'] = stocks['公司'].map(lambda x: dict_company_names[x.lower()])

2. apply用于Series和DataFrame的转换

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

① Series.apply(function)

function的参数是Series的每个值

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

② DataFrame.apply(function)

function的参数是对应轴的Series

stocks['公司中文4'] = stocks.apply(lambda x: dict_company_names[x['公司'].lower()], axis = 1)
# 注意:
# 1、apply是在stocks这个DataFrame上调用
# 2、lambda x的x是一个Series,因为指定了axis=1,所以Series的key是列名,可以用x['公司']获取

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

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

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

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

十七、对每个分组应用apply

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

GroupBy.apply(function)

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

实例演示:

1. 怎样对数值列按分组的归一化

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

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

归一化公式:
Xnormalized=(XXminimum)(XmaxmumXminimum) Xnormalized = \frac{(X-Xminimum)}{(Xmaxmum-Xminimum)}

用户对电影评分的归一化

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

import pandas as pd

df_ratings = pd.read_csv('ratings.dat'
                       , sep = '::'
                       ,engine = 'python'
                       , names = 'UserID::MovieID::Rating::Timestamp'.split('::'))
df_ratings.head()

# 自定义一个函数,实现按照用户ID分组,然后对其中一列归一化
def df_ratings_norm(df):
    min_value = df['Rating'].min()
    max_value = df['Rating'].max()
    df['Rating_norm'] = df['Rating'].apply(lambda x: (x-min_value)/(max_value-min_value))
    return df

# 分组归一化
df_ratings = df_ratings.groupby('UserID').apply(df_ratings_norm)

2. 怎样取每个分组的TOPN数据

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

import pandas  as pd

df = pd.read_csv('beijing_tianqi_2018.csv')

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

# 新增一列为月份
df['month']=df['ymd'].str[:7]

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

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

注:groupby的apply函数返回的dataframe,其实可以和原来的dataframe完全不一样

十八、使用stack和pivot实现数据透视

将列式数据变成二维交叉形式,便于分析,叫做重塑或透视

1. 经过统计得到多维度指标数据

非常场景的统计场景、指定多个维度,计算聚合后的指标

实例:统计得到“电影评分数据集”,每个月份的每个分数被评分多少次:(月份、分数1~5、次数)

import pandas as pd
import numpy as np
%matplotlib inline

df_ratings = pd.read_csv('ratings.dat'
                       , sep = '::'
                       ,engine = 'python'
                       , names = 'UserID::MovieID::Rating::Timestamp'.split('::'))
df_ratings.head()

# 添加一列,将时间戳变为日期格式
df_ratings['pdate'] = pd.to_datetime(df_ratings['Timestamp'],unit = 's')
df_ratings.head()

# 查看每列数据类型
df_ratings.dtypes

# 实现数据统计
df_ratings_group = df_ratings.groupby([df_ratings['pdate'].dt.month,'Rating'])['UserID'].agg(pv=np.size)

对这样格式的数据,想按月份、不同评分的次数趋势,是无法实现的
需要将数据变换成每个评分是一列,才可以实现

2. 使用unstack实现数据二维透视

目的:想要画图对比按照月份的不同评分的数量趋势

df_ratings_stack = df_ratings_group.unstack()
df_ratings_stack

# 绘图查看趋势变化
df_ratings_stack.plot()

# unstack 和 stack是互匿操作
df_ratings_stack.stack().head(20)

3. 使用pivot简化透视

pivot方法相当于对df使用set_index创建分层索引,然后调用unstack

# 将索引列转化为普通列
df_ratings_reset = df_ratings_group.reset_index()

# 通过pivot方法简化透视
df_ratings_pivot = df_ratings_reset.pivot('pdate', 'Rating','pv')

4. stack、unstack、pivot语法

① stack

DataFrame.stack(level = -1,dropna = True),将column变成index,类似把横放的书籍变成竖放

  • level = -1代表多层索引的最内层,可以通过==0、1、2指定多层索引的对应层

② unstack

DataFrame.unstack(level = -1,fill_value = None),将index变成column,类似把竖放的书籍变成横放

③ pivot

DataFrame.pivot(index = None, columns = None, values =None),指定index、columns、values实现二维透视

十九、apply函数给表格同时添加多列

# 自定义一个函数,返回值为多个Series对象
def myfunc(row):
    newa,newb = row['a'] + row['b'],row['a'] - row['b']
    return newa, newb

# 同时添加多列
df[['newa','newb']] = df.apply(myfunc, axis= 1, result_type ='expand')

二十、SettingWithCopyWarning报警

产生原因:
链式操作是两个步骤,先get再set,get得到的dataframe可能是view也可能是copy,pandas发出警告

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

解决方法:
方法一:将get+set的两步操作,改成set的一步操作
方法二:如果需要预筛选数据做后续的处理分析,使用copy复制dataframe

总结:
Pandas不允许先筛选子dataframe,再进行修改写入
要么使用.loc实现一个步骤直接修改源dataframe
要么先复制一个子dataframe再一个步骤执行修改