一、读取数据
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种方法
- 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>值
- df.iloc方法
根据行、列的数字位置查询
- df.where方法
- 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
- 机器学习模型学的更快性能更好
归一化公式:
用户对电影评分的归一化
每个用户的评分不同,有的乐观派评分高,有的悲观派评分低,按用户做归一化
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再一个步骤执行修改