编辑器:jupyter notebook
一、数据统计
import numpy as np
import pandas as pd
1. 创建DataFrame对象
- 生成100行3列[60,100)的随机整数
data = np.random.randint(60, 100, [100, 3])
index = range(1, 101)
columns = ['语文', '数学', '英语']
df = pd.DataFrame(
data=data,
index=index,
columns=columns)
df
2. 计算总成绩
- 增加一列数据
df['总成绩'] = df.sum(axis=1)
df.head()
3. 计算各科均值
- 增加一行数据
df.loc['均值'] = df.mean(axis=0)
df
4. 求每科的最大值
df.max()
语文 99.0 数学 99.0 英语 99.0 总成绩 564.0 dtype: float64
5. 求每科的最小值
df.min()
语文 60.0 数学 61.0 英语 60.0 总成绩 414.0 dtype: float64
6. 求每科的中位数
df.median()
语文 82.0 数学 81.0 英语 80.0 总成绩 484.0 dtype: float64
7. 求每科的众数
df.mode()
df['总成绩'].mode() # 获取总成绩的众数
0 492.0 dtype: float64
# 如果存在多个众数,则求均值
df['总成绩'].mode().median()
492.0
8. 求数学的众数
- 如存在多个众数,则求它们的均值
df['数学'].mode()
0 90.0 dtype: float64
9. 方差和标准差
- 生成2行10列[60,100)的随机整数
np.random.seed(666) # 随机种子
data1 = np.random.randint(60,100,[2,10])
data2 = np.random.randint(60,120,[2,10])
data = np.vstack((data1,data2)) # 拼接data1,data2
index = ['张三','里斯','王五','赵六']
columns = [f"第{i}次测试" for i in range(1,11)]
data
array([[ 62, 90, 66, 69, 90, 96, 87, 90, 91, 88], [ 74, 93, 76, 99, 65, 78, 72, 75, 73, 65], [113, 80, 71, 84, 80, 96, 108, 108, 96, 90], [106, 118, 107, 76, 73, 96, 86, 65, 102, 99]])
df = pd.DataFrame(data=data,index=index,columns=columns)
df
10. 求方差、标准差和变异系数
df['方差'] = df.iloc[:,0:10].var(axis=1)
df['标准差'] = df.iloc[:,0:10].std(axis=1)
df['均值'] = df.iloc[:,0:10].mean(axis=1)
df['变异系数'] = df['标准差'] / df['均值']
df[['方差','标准差','均值','变异系数']].head()
11. 分位数
- 随机生成指定均值及标准差的数组
- 均值为90,标准差为2000,100个元素
data = np.random.normal(90,2000,100)
columns = ['利润']
df = pd.DataFrame(data=data,columns=columns)
df
- 计算25%的分位数,Q1,下四分位数
# .quantile(q=0.5, axis=0, numeric_only=True, interpolation='linear')
Q1 = df['利润'].quantile(0.25)
Q1
2808.5524129961677
- 计算75%的分位数,Q3,上四分位数
Q3 = df['利润'].quantile(0.75)
Q3
2808.5524129961677
- 计算四分位距,Q3-Q1
IQR = Q3 - Q1
IQR
- 异常值检测
- 设定上限值和下限值
# 其中1.5可以根据需求修改
UPPER = min(Q3 + 1.5*IQR, df['利润'].max()) # 上限值,不超过最大值
LOWER = max(Q1 - 1.5*IQR, df['利润'].min()) # 下限值,不低于最小值
- 异常值筛选
df[(df['利润'] > UPPER) | (df['利润'] < LOWER) ]
- 异常值处理
- 如高于上限值,则使用上限值替换
- 如低于下限值,则使用下限值替换
UPPER,LOWER
# (2808.5524129961677, 2808.5524129961677)
df['利润'] = df['利润'].clip(LOWER,UPPER)
df
二、数据格式化
import numpy as np
import pandas as pd
1. 设置小数位数
- 生成5行5列[0, 1)的随机小数
df = pd.DataFrame(
data=np.random.rand(5, 5),
columns=['A', 'B', 'C', 'D', 'E'])
df
- 所有列保留2位小数
# .round(decimals=0, *args, **kwargs)
df.round(2)
- 使用字典方法
- A列保留1位小数
- B列保留2位小数
df.round({'A':1,'B':2})
- 使用Series方法
- C列保留1位小数
- D列保留2位小数
s = pd.Series(index=['C','D'],data=[1,2])
df.round(s)
2. 设置百分比
2.1 apply方法
- Series和DataFrame对象均有此方法
- 指定列保留0位小数的百分比
# .apply(func, axis=0, raw=False, result_type=None, args=(), **kwds)
df['A'].apply(lambda x: '{:.0%}'.format(x))
0 69% 1 46% 2 19% 3 5% 4 45% Name: A, dtype: object
- 指定列保留2位小数的百分比
# .apply(func, axis=0, raw=False, result_type=None, args=(), **kwds)
df['A'].apply(lambda x: '{:.2%}'.format(x))
0 68.96% 1 45.66% 2 18.66% 3 4.97% 4 44.94% Name: A, dtype: object
- 所有列保留2位小数的百分比
df.apply(lambda x: '{:.2%}'.format(x),axis=1) # apply解决不了多列格式化,但是可以多列计算
TypeError: unsupported format string passed to Series.__format__
df.apply(lambda x: x*2,axis=1)
# for循环 + apply 实现所有列保留2位小数的百分比
tb = df.copy()
for col in tb.columns:
tb[col] = tb[col].apply(lambda x: '{:.2%}'.format(x))
tb
2.2 applymap方法
- DataFrame对象的方法,不支持单列的操作
- 所有列保留2位小数的百分比
df.applymap(lambda x: '{:.2%}'.format(x))
2.3 map方法
- Series对象的方法,不支持多列的操作
- 指定列保留2位小数的百分比
df['A'].map(lambda x: '{:.2%}'.format(x))
0 68.96% 1 45.66% 2 18.66% 3 4.97% 4 44.94% Name: A, dtype: object
- map字典操作,字段值映射
td = pd.DataFrame(data=[[1,187],
[0,170]],
columns=['性别','身高'])
td
dct = {1:'男',0:'女'}
td['性别'] = td['性别'].map(dct)
td
3. 设置千位分隔符
- 生成10行5列[1000, 10000)的随机整数
df = pd.DataFrame(
data=np.random.randint(1000, 10000, [10, 5]),
columns=['A', 'B', 'C', 'D', 'E'])
df
#指定列生成千位分隔符
df['A'].apply(lambda x: '{:,}'.format(x)) # 转换后类型为字符串
0 6,202 1 7,905 2 6,277 3 1,032 4 6,132 5 6,689 6 4,470 7 8,991 8 8,596 9 6,119 Name: A, dtype: object
# 所有列设置千位分隔符
df.applymap(lambda x: '{:,}'.format(x))
三、数据分组聚合
import pandas as pd
df = pd.read_excel('./data/demo_03.xlsx', sheet_name='Sheet1')
df
1. groupby分组函数
- groupby函数 + 聚合函数
1.1 一列分组一列聚合
df.groupby('一级分类')['数量'].sum().to_frame()
df.groupby('一级分类')['数量'].sum().reset_index()
1.2 一列分组多列聚合
df.groupby('一级分类')[['数量','金额']].sum()
1.3 多列分组多列聚合
df.groupby(['一级分类','二级分类'])[['数量','金额']].sum()
df.groupby(['一级分类','二级分类'])[['数量','金额']].sum().reset_index()
2. 对分组数据进行遍历
df.groupby('一级分类')['数量'].sum().to_frame()
groups = df[['订单编号','一级分类']].groupby('一级分类')
for key,data in groups:
print(key)
print(data)
print("-"*100)
办公 订单编号 一级分类 0 US-2021-1357144 办公 1 CN-2021-1973789 办公 2 CN-2021-1973789 办公 3 US-2021-3017568 办公 4 CN-2020-2975416 办公 ... ... ... 9948 CN-2018-1902956 办公 9949 CN-2018-1902956 办公 9951 CN-2019-3665640 办公 9955 CN-2021-4318875 办公 9958 CN-2018-3557528 办公 [5687 rows x 2 columns] ---------------------------------------------------------------------------------------------------- 家具 订单编号 一级分类 7 CN-2019-4497736 家具 24 US-2020-4150614 家具 25 US-2020-4150614 家具 30 US-2020-4150614 家具 33 CN-2021-3230180 家具 ... ... ... 9931 CN-2020-3557528 家具 9942 CN-2019-3438839 家具 9950 CN-2018-1902956 家具 9953 CN-2021-5146329 家具 9956 CN-2021-4318875 家具 [2244 rows x 2 columns] ---------------------------------------------------------------------------------------------------- 技术 订单编号 一级分类 5 CN-2019-4497736 技术 10 CN-2018-4195213 技术 11 CN-2021-5801711 技术 13 CN-2019-2752724 技术 14 CN-2019-2752724 技术 ... ... ... 9935 CN-2019-1364986 技术 9939 US-2021-4180783 技术 9952 US-2018-5128542 技术 9954 CN-2021-4318875 技术 9957 CN-2021-4318875 技术 [2028 rows x 2 columns] ----------------------------------------------------------------------------------------------------
3. groupby+agg函数
3.1 指定列使用多个聚合函数
df.groupby('一级分类')[['数量','金额']].agg(['mean','sum'])
3.2 指定列使用指定聚合函数
df.groupby('一级分类').agg({'数量':'mean','金额':'sum'})
df.groupby('一级分类').agg({'数量':['mean','sum'],'金额':['sum','max']})
df.groupby('一级分类').agg({'数量':'mean','金额':'max'}).rename(columns={'数量':'数量均值','金额':'金额最大值'})
df.groupby('一级分类').agg(数量均值=('数量','mean'),金额最大值=('金额','max'))
df.groupby('一级分类')['金额'].agg([('销售总额','sum'),('平均金额','mean')])
4. 自定义函数分组统计
- 统计频数最高(行计数最多)的二级分类
- 以及总数量和总金额
df['二级分类'].value_counts().index[0]
'装订机'
df.agg({'数量':np.sum,'金额':'sum','二级分类':lambda x: x.value_counts().index[0]}).reset_index() # np ——> numpy的别名,需要引入numpy
- 以一级分类为分组,统计销售数量最多的二级分类
# argmax:原始索引
# idxmax:自定义索引
df[df['一级分类'] == '办公'].groupby('二级分类')['数量'].sum().idxmax()
'装订机'
# 一级分类:办公/技术/家具
grouped = df.groupby('一级分类')
grouped.apply(lambda x: x.groupby('二级分类')['数量'].sum().idxmax())
一级分类 办公 装订机 家具 椅子 技术 电话 dtype: object
5. groupby+transform函数
- pd.DataFrame.transform()
- 统计各省的金额以及占所属大区的比例
tb = df.groupby(['大区','省份']).agg({'金额':'sum'}).reset_index()
tb.head()
tb['大区金额'] = tb.groupby('大区')['金额'].transform('sum')
tb.head()
tb['大区金额占比'] = tb['金额'] / tb['大区金额']
tb['大区金额占比'] = tb['大区金额占比'].apply(lambda x: '{:.2%}'.format(x)) # 将数据转换为百分比格式
tb.head()
6. 多列合并计算
6.1 groupby+字典+聚合函数
df = pd.read_excel('./data/demo_03.xlsx', sheet_name='Sheet2')
df.head()
# 合并北上广深
dct = {'北京销量':'北上广深',
'上海销量':'北上广深',
'广州销量':'北上广深',
'深圳销量':'北上广深',
'长沙销量':'长沙'}
# 先设置行索引
df = df.set_index('二级分类')
df
df.groupby(dct,axis=1).sum()
6.2 groupby+Series+聚合函数
df = pd.read_excel('./data/demo_03.xlsx', sheet_name='Sheet2')
series = pd.Series(data=['北上广深','北上广深','北上广深','北上广深','长沙'],
index=['北京销量','上海销量','广州销量','深圳销量','长沙销量'])
df = df.set_index('二级分类')
df.groupby(series,axis=1).sum()
7. pivot_table透视表
df = pd.read_excel('./data/demo_03.xlsx', sheet_name='Sheet1')
# pd.pivot_table(
# data, ——> df对象
# values=None, ——>Excel数据透视表的值
# index=None, ——> 数据透视表的行
# columns=None, ——> 数据透视表的列
# aggfunc='mean', ——> 数据透视表的计算类型
# fill_value=None, ——> 填充空值
# margins=False, ——> 是否显示合计
# dropna=True, ——> 是否删除缺失值(如整行缺失)
# margins_name='All', ——> 合计的别名
# observed=False,
# ) -> 'DataFrame'
tb = pd.pivot_table(
data = df,
index='大区',
columns='一级分类',
values='金额',
aggfunc='sum',
margins=True,
margins_name='合计')
tb = tb.reset_index()
# 重命名列索引的名称为None
tb.columns.name = None
tb
tb = pd.pivot_table(
data = df,
index=['大区','省份'],
columns='一级分类',
values='金额',
aggfunc='sum',
margins=True,
margins_name='合计')
tb
8. crosstab交叉表
df = pd.read_excel('./data/demo_03.xlsx', sheet_name='Sheet1')
# pd.crosstab(
# index, ——> 行索引
# columns, ——> 列索引
# values=None, ——> 聚合字段
# rownames=None, ——> 行索引别名
# colnames=None, ——> 列索引别名
# aggfunc=None, ——> 聚合函数
# margins=False, ——> 是否显示合计
# margins_name: str = 'All', ——> 合计别名
# dropna: bool = True, ——> 是否删除缺失行(如整行缺失)
# normalize=False, ——> 值显示方式(True/all:总计百分比,index:行汇总百分比,columns:列汇总百分比)
# ) -> 'DataFrame'
pd.crosstab(
index=df['大区'],
columns=df['一级分类'],
values=df['数量'],
aggfunc='sum',
margins=True,
margins_name='合计',
normalize=True
)
pd.crosstab(
index=df['大区'],
columns=df['一级分类'],
values=df['数量'],
aggfunc='sum',
margins=True,
margins_name='合计',
normalize='index' # 列汇总百分比
)
9. category数据类型
- 主要为了提升运算性能
df = pd.read_csv('./data/demo_03_cat.csv')
df.dtypes
大区 object 金额 int64 dtype: object
- 测试非category数据类型的运算时间
%%time
df.groupby('大区')['金额'].sum()
Wall time: 138 ms
Out[34]:
大区 东北 96128025 中南 96518713 华东 96023787 华北 96369953 西北 96451240 西南 95335807 Name: 金额, dtype: int64
- 测试category数据类型的运算时间
df['大区'] = df['大区'].astype('category')
%%time
df.groupby('大区')['金额'].sum()
Wall time: 32 ms
Out[36]:
大区 东北 96128025 中南 96518713 华东 96023787 华北 96369953 西北 96451240 西南 95335807 Name: 金额, dtype: int64
四、数据偏移与切分
import numpy as np
import pandas as pd
df = pd.read_excel('./data/demo_04.xlsx', sheet_name='Sheet1')
df.head()
1. 数据偏移
1.1 shift函数
- 销量增长率 = (本期销量 - 上期销量) / 上期销量
# 日期不连续,必须设置时间序列为行索引
df = df.set_index('日期')
# df.shift(
# periods=1,
# freq=None,
# axis=0,
# fill_value=<object object at 0x000001B071412510>,
# ) -> 'DataFrame'
df['上期销量'] = df['销量'].shift(periods=1, freq='D')
df['销量增长率'] = (df['销量'] - df['上期销量']) / df['上期销量']
df.head()
1.2 diff函数
- 偏移后计算差异
# 不支持freq时间频率参数
# df.diff(periods: 'int' = 1, axis: 'Axis' = 0) -> 'DataFrame'
df['diff'] = df['销量'].diff(periods=1)
df.head()
1.3 pct_change函数
- 偏移后计算差异百分比
# df.pct_change(
# periods=1,
# fill_method='pad',
# limit=None,
# freq=None,
# **kwargs,
# ) -> 'FrameOrSeries'
df['pct_change'] = df['销量'].pct_change(periods=1,freq='D')
df.head()
1.4 rolling函数
- 滚动计算,计算近3天平均销量
# df.rolling(
# window: 'Union[int, timedelta, BaseOffset, BaseIndexer]', ——> 设置窗口范围
# min_periods: 'Optional[int]' = None, ——> 解决窗口范围不足导致返回值为空的统计问题
# center: 'bool_t' = False,
# win_type: 'Optional[str]' = None,
# on: 'Optional[str]' = None,
# axis: 'Axis' = 0,
# closed: 'Optional[str]' = None,
# )
df['近三天平均销量'] = df['销量'].rolling(window=3).mean()
df.head()
df['近三天平均销量'] = df['销量'].rolling(window=3,min_periods=1).mean()
df.head()
2. 数据切分
tb = pd.read_excel('./data/demo_04.xlsx', sheet_name='Sheet2')
tb.head()
2.1 cut函数
2.1.1 指定箱数,等距分箱
- 10组
# pd.cut(
# x,
# bins, ——> 组数
# right: bool = True,
# labels=None,
# retbins: bool = False,
# precision: int = 3,
# include_lowest: bool = False,
# duplicates: str = 'raise',
# ordered: bool = True,
# )
tb['等距分箱'] = pd.cut(tb['成绩'],bins=10,right=False) # right = False,左闭右开, 默认左开右闭
tb['等距分箱'].value_counts()
[36.4, 48.2) 17 [107.2, 119.118) 15 [1.0, 12.8) 11 [24.6, 36.4) 10 [71.8, 83.6) 10 [60.0, 71.8) 9 [95.4, 107.2) 9 [48.2, 60.0) 7 [12.8, 24.6) 6 [83.6, 95.4) 6 Name: 等距分箱, dtype: int64
2.1.2 指定区间分箱
- [0, 60, 80, 100, 120]
tb['指定区间分箱'] = pd.cut(x=tb['成绩'],bins=[0, 60, 80, 100, 120],right=False)
tb['指定区间分箱'].value_counts()
[0, 60) 51 [100, 120) 22 [60, 80) 17 [80, 100) 10 Name: 指定区间分箱, dtype: int64
# labels 分箱的标签
tb['指定区间分箱'] = pd.cut(x=tb['成绩'],bins=[0, 60, 80, 100, 120],labels=['不及格','一般','良好','优秀'],right=False)
tb['指定区间分箱'].value_counts()
不及格 51 优秀 22 一般 17 良好 10 Name: 指定区间分箱, dtype: int64
2.2 qcut函数
2.2.1 指定分位数,等频分箱
- 4组
# pd.qcut(
# x,
# q,
# labels=None,
# retbins: bool = False,
# precision: int = 3,
# duplicates: str = 'raise',
# )
tb['等频分组'] = pd.qcut(x=tb['成绩'],q=4) # 默认左开右闭
tb['等频分组'].value_counts()
(0.999, 34.75] 25 (34.75, 55.5] 25 (55.5, 94.25] 25 (94.25, 119.0] 25 Name: 等频分组, dtype: int64
2.2.2 指定分位数区间分箱
- [0, 0.3, 0.5, 0.75, 1]
tb['指定分位数区间分箱'] = pd.qcut(x=tb['成绩'],q=[0, 0.3, 0.5, 0.75, 1])
tb['指定分位数区间分箱'].value_counts()
(0.999, 38.7] 30 (55.5, 94.25] 25 (94.25, 119.0] 25 (38.7, 55.5] 20 Name: 指定分位数区间分箱, dtype: int64
tb['成绩'].quantile(0.3) # 38.7
tb['成绩'].median() # 55.5
tb['成绩'].quantile(0.75) # 94.25
tb['成绩'].max() # 119
五、数据结构转换
import pandas as pd
1. 一列转为多列
- split方法,已确定拆分结果的列数
- 只将省份和城市拆分为新字段
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet1')
df
tb = df['收货地址'].str.split(' ',expand=True)
df['省份'] = tb[0]
df['城市'] = tb[1]
df
df['收货地址'].str.split(' ',expand=False)
0 [广东省, 深圳市, 光明区, 公明街道] 1 [广东省, 深圳市, 光明区, 公明街道] 2 [广东省, 深圳市, 光明区, 公明街道] 3 [广东省, 深圳市, 宝安区, 沙井街道] 4 [广东省, 深圳市, 宝安区, 石岩街道] Name: 收货地址, dtype: object
- 将拆分结果全部转为新字段
df[['省','市','区','街道']] = df['收货地址'].str.split(' ',expand=True)
df
2. 一列转为多列
- split和join方法,不确定拆分结果的列数
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet2')
df
# 先拆分商品信息,再关联至原表
df_split = df['商品信息'].str.split(',',expand=True)
df_split
# df.join(other, on=None, how='left', lsuffix='', rsuffix='', sort=False) -> 'DataFrame'
# 如果未指定关联键,则通过索引字段关联
df = df.join(df_split)
df
3. 对元组/列表形式的字段进行拆分
df = pd.DataFrame({
'订单编号': [1, 2, 3, 4],
'数量和单价': [(1, 2),
(3, 4),
(5, 6),
(7, 8)]})
df
df[['数量','单价']] = df['数量和单价'].apply(pd.Series)
df
4. 列转为行
4.1 stack方法
- 将除索引列外的字段转为最内层的行索引
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet3')
df
# 将除索引列外的字段转为最内层的行索引
tb = df.set_index('门店')
tb
tb = tb.stack().reset_index()
tb.columns = ['门店','品类','销量']
tb
4.2 melt方法
- 宽表转为长表
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet3')
df
# pd.melt(
# frame: 'DataFrame',
# id_vars=None, ——————> 位置保持不变的字段
# value_vars=None, ——————> 需要转换的字段
# var_name=None, ——————> variable的别名
# value_name='value', ——————> value的别名
# col_level=None,
# ignore_index: bool = True,
# ) -> 'DataFrame'
pd.melt(frame=df,id_vars='门店',value_vars=['手机','电脑'],var_name='品类',value_name='销量')
5. 行转为列
5.1 unstack方法
- 将最内层的行索引转为字段
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet4')
df
tb = df.set_index(['门店','品类'])
tb
tb = tb.unstack()
tb.columns = tb.columns.droplevel(0)
tb.columns
Index(['手机', '电脑'], dtype='object', name='品类')
tb.columns.name = None
tb.reset_index()
5.2 pivot方法
- 透视列,没有聚合功能
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet4')
df
# pivot 没有聚合
# pivot_table 可以聚合
# .pivot(index=None, columns=None, values=None) -> 'DataFrame'
tb = df.pivot(index='门店',columns='品类',values='销量')
tb
tb.columns.name = None
tb.reset_index()
6. DataFrame转为字典
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet5')
df.head()
tb = df.set_index('用户ID')
tb.to_dict()
'''
{'用户姓名': {'uid00160': '刘备',
'uid00161': '关羽',
'uid00162': '张飞',
'uid00182': '赵云',
'uid00188': '马超'}}
'''
tb.to_dict()['用户姓名']
'''
{'uid00160': '刘备',
'uid00161': '关羽',
'uid00162': '张飞',
'uid00182': '赵云',
'uid00188': '马超'}
'''
7. DataFrame转为列表
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet5')
df.head()
df.values.tolist()
'''
[['uid00160', '刘备'],
['uid00161', '关羽'],
['uid00162', '张飞'],
['uid00182', '赵云'],
['uid00188', '马超']]
'''
8. DataFrame转为元组
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet5')
df
tuple(tuple(x) for x in df.values)
'''
(('uid00160', '刘备'),
('uid00161', '关羽'),
('uid00162', '张飞'),
('uid00182', '赵云'),
('uid00188', '马超'))
'''
9. DataFrame转为HTML
df = pd.read_excel('./data/demo_05.xlsx', sheet_name='Sheet5')
df
df.to_html('网页.html')
六、数据合并
import pandas as pd
1. merge横向合并
df1 = pd.read_excel('./data/demo_06.xlsx', sheet_name='基本信息')
df2 = pd.read_excel('./data/demo_06.xlsx', sheet_name='联系方式')
df1
df2
1.1 左连接
# pd.merge(
# left, ——————> 左表
# right, ——————> 右表
# how: str = 'inner', ——————> 连接方式,默认内链接
# on=None, ——————> 关联字段
# left_on=None, ——————> 左表的关联字段
# right_on=None, ——————> 右表的关联字段
# left_index: bool = False,
# right_index: bool = False,
# sort: bool = False, ——————> 排序,默认降序
# suffixes=('_x', '_y'),
# copy: bool = True,
# indicator: bool = False, ——————> 指示器,值为表中的数据是否两个表中都存在或只存在于一个表中
# validate=None,
# ) -> 'DataFrame'
pd.merge(df1,df2,how='left',left_on='工号',right_on='编号',indicator=True)
1.2 右连接
pd.merge(df1,df2,how='right',left_on='工号',right_on='编号',indicator=True)
1.3 外连接
pd.merge(df1,df2,how='outer',left_on='工号',right_on='编号',indicator=True)
1.4 内连接
pd.merge(df1,df2,how='inner',left_on='工号',right_on='编号',indicator=True)
1.5 交叉连接
- 笛卡尔积
pd.merge(df1,df2,how='cross') # 类似于两表相成
2. join横向合并
df1 = pd.read_excel('./data/demo_06.xlsx', sheet_name='基本信息')
df2 = pd.read_excel('./data/demo_06.xlsx', sheet_name='联系方式')
df1
df2
# 先将将两表的关联字段设置为行索引
df1.set_index('工号',inplace=True)
df2.set_index('编号',inplace=True)
2.1 左连接
df1.join(df2,how='left')
2.2 右连接
df1.join(df2,how='right')
2.3 外连接
df1.join(df2,how='outer')
2.4 内连接
df1.join(df2,how='inner')
3. concat横向合并
df1 = pd.read_excel('./data/demo_06.xlsx', sheet_name='基本信息')
df2 = pd.read_excel('./data/demo_06.xlsx', sheet_name='联系方式')
df1
df2
# 先将将两表的关联字段设置为行索引
df1.set_index('工号',inplace=True)
df2.set_index('编号',inplace=True)
3.1 外连接
# pd.concat(
# objs: Union[Iterable[ForwardRef('NDFrame')], Mapping[Union[Hashable, NoneType], ForwardRef('NDFrame')]],
# axis=0,
# join='outer',
# ignore_index: bool = False,
# keys=None,
# levels=None,
# names=None,
# verify_integrity: bool = False,
# sort: bool = False,
# copy: bool = True,
# ) -> Union[ForwardRef('DataFrame'), ForwardRef('Series')]
pd.concat(objs=[df1,df2],axis=1,join='outer')
3.2 内连接
pd.concat(objs=[df1,df2],axis=1,join='inner')
4. concat纵向合并
df3 = pd.read_excel('./data/demo_06.xlsx', sheet_name='1月订单')
df4 = pd.read_excel('./data/demo_06.xlsx', sheet_name='2月订单')
df3
df4
pd.concat(objs=[df3,df4],axis=0,ignore_index=True) # ignore_index=True 保持索引号的连续性
# 使用append方法实现
df3.append(df4,ignore_index=True) # ignore_index=True 保持索引号的连续性
5.总结
表的横向合并:merge方法(支持交叉连接),pd.merge 或 df.merge;join方法(不支持交叉连接),连式写法 df.join;
表的纵向合并:concat方法(横向合并仅支持内外连接)或 append方法
七、数据导出
import pandas as pd
# pip install sqlalchemy
from sqlalchemy import create_engine
import pymysql
df = pd.read_excel('./data/demo_07.xlsx')
df.head()
1. 导出Excel
df.to_excel('导出Excel.xlsx',sheet_name='订单明细1',index=False) # index = False 不导入索引
2. 导出CSV
df.to_csv('导出CSV.csv',index=False)
3. 导出TxT
df.to_csv('导出Txt.txt',sep='\t',index=False) # sep 设置分隔符
4. 导出Json
- orient: {'split', 'records', 'index', 'columns', 'values', 'table'}
df.to_json('导出Json.json',orient='split',force_ascii=False) # orient 设置结构类型;force_ascii=False 取消ascii编码格式以显示汉字
5. 导出数据库
engine = create_engine('mysql+pymysql://用户名:密码@localhost:3306/库名?charset=utf8')
# df.to_sql(
# name: 'str', ——————> 表名
# con, ——————> 连接引擎
# schema=None, ——————> 库名
# if_exists: 'str' = 'fail', ——————> replace: 如果表存在则新建,存在则覆盖 ;append:如果表存在则新建,存在则追加
# index: 'bool_t' = True, ——————> False:不含索引
# index_label=None,
# chunksize=None,
# dtype=None,
# method=None,
# ) -> 'None'
df.to_sql(schema='库名',name='表名',con=engine,index=False,if_exists='append')
6. 导出Excel
- 同一工作簿下的不同工作表
df1 = pd.read_excel('./data/demo_07.xlsx', sheet_name='1月订单')
df2 = pd.read_excel('./data/demo_07.xlsx', sheet_name='2月订单')
df1.head()
df2.head()
# 创建数据写入器
write = pd.ExcelWriter('导出Excel不同表.xlsx')
# 写入
df1.to_excel(write,sheet_name='1月订单')
df2.to_excel(write,sheet_name='2月订单')
# 保存
write.save()
# 关闭
write.close()
八、日期处理与采样
import pandas as pd
1. 解析日期
df = pd.read_excel('./data/demo_08.xlsx', sheet_name='Sheet1')
df
# pd.to_datetime(
# arg: Union[~DatetimeScalar, List, Tuple, ~ArrayLike, ForwardRef('Series')],
# errors: str = 'raise', ——————————> ignore:当无法解析时直接忽略;raise:无法解析时抛出异常;coerce:无法解析返回空值,强制转换
# dayfirst: bool = False,
# yearfirst: bool = False,
# utc: Union[bool, NoneType] = None,
# format: Union[str, NoneType] = None,
# exact: bool = True,
# unit: Union[str, NoneType] = None,
# infer_datetime_format: bool = False,
# origin='unix',
# cache: bool = True,
# ) -> Union[pandas.core.indexes.datetimes.DatetimeIndex, ForwardRef('Series'), ~DatetimeScalar, ForwardRef('NaTType')]
df['订单日期'] = pd.to_datetime(df['订单日期'])
df
2. 组合日期
df = pd.read_excel('./data/demo_08.xlsx', sheet_name='Sheet2')
df
df['日期'] = pd.to_datetime(df)
df
3. 提取日期信息
df = pd.read_excel('./data/demo_08.xlsx', sheet_name='Sheet3')
df
df['订单日期'] = pd.to_datetime(df['订单日期'])
df
df['年'] = df['订单日期'].dt.year
df['月'] = df['订单日期'].dt.month
df['季度'] = df['订单日期'].dt.quarter
df['日'] = df['订单日期'].dt.day
df['周几'] = df['订单日期'].dt.day_name()
df['是否年底'] = df['订单日期'].dt.is_year_end
df
df.groupby(['年','月']).size()
年 月 2020 1 1 2 1 3 1 6 1 9 1 12 1 dtype: int64
4. 重采样
- 针对日期字段分组聚合的快捷方式
- 重采样:升采样,降采样
- 升采样:低频率到高频率采样
- 降采样:高频率到低频率采样
- Time series / date functionality — pandas 1.5.0 documentation
- 时间频率:
- AS: 年初
- A: 年末
- YS: 年初
- Y: 年末
- QS: 季初
- Q: 季末
- MS: 月初
- M: 月末
- W: 周
- D: 日
- H: 时
- T: 分
- min: 分
- S: 秒
- W-SUN: 周(周日结束)Sunday
- W-MON: 周(周一结束)Monday
- W-TUE: 周(周二结束)Tuesday
- W-WED: 周(周三结束)Wednesday
- W-THU: 周(周四结束)Thursday
- W-FRI: 周(周五结束)Friday
- W-SAT: 周(周六结束)Saturday
df = pd.read_excel('./data/demo_08.xlsx', sheet_name='Sheet4')
df.head()
4.1 设置日期为索引
tb = df.set_index('订单日期')
4.2 按月统计数据
# tb.resample(
# rule,
# axis=0,
# closed: 'Optional[str]' = None,
# label: 'Optional[str]' = None,
# convention: 'str' = 'start',
# kind: 'Optional[str]' = None,
# loffset=None,
# base: 'Optional[int]' = None,
# on=None,
# level=None,
# origin: 'Union[str, TimestampConvertibleTypes]' = 'start_day',
# offset: 'Optional[TimedeltaConvertibleTypes]' = None,
# ) -> 'Resampler'
tb.resample('M')['销售额'].sum().to_period('M').to_frame()
tb.resample('M').agg({'数量':'sum','销售额':'mean'}).to_period('M')
4.3 按季统计数据
tb.resample('Q').agg({'数量':'sum','销售额':'mean'}).to_period('Q')
4.4 按年统计数据
tb.resample('Y').agg({'数量':'sum','销售额':'mean'}).to_period('Y')
4.5 日期切片操作
# 必须先设置日期为索引,排序后再进行切片处理
tb = tb.sort_index()
tb['2020-01-01':'2020-01-15']
5. OHLC函数
- open(开始值,开盘价)
- high(最高值)
- low(最低值)
- close(结束值,收盘价)
# pandas: 早起主要做金融数据分析
tb.resample('M')['销售额'].ohlc().to_period('M')
6. sample函数
- 随机抽样
tb = pd.DataFrame(
data=[['貂蝉', 4],
['西施', 3],
['王昭君', 2],
['杨玉环', 1]],
columns=['贵妃', '权重'])
tb
6.1 随机抽取1个
# tb.sample(
# n=None, ————————> 指定数量抽样
# frac=None, ————————> 指定占比抽样
# replace=False, ————————> 是否允许重复抽样
# weights=None, ————————> 按照权重抽样,权重数值越大越容易抽中
# random_state=None, ————————> 设置随机种子
# axis=None,
# ) -> 'FrameOrSeries'
tb.sample(n=1)
6.2 随机抽取50%
tb.sample(frac=0.5)
6.3 允许重复抽样
tb.sample(n=2,replace=True)
6.4 按权重抽样
df1 = pd.DataFrame()
for i in range(1000):
df2 = tb.sample(n=1,weights='权重')
df1 = df1.append(df2)
df1.value_counts()
贵妃 权重 貂蝉 4 395 西施 3 293 王昭君 2 200 杨玉环 1 112 dtype: int64
6.5 设置随机种子
tb.sample(n=1,random_state=3)
九、时间序列
import pandas as pd
import arrow
# github时间模块标星最高的第三方库
# pip install arrow
1. arrow时间模块
- 获取当前时间
arrow.now()
<Arrow [2022-09-27T15:48:40.889286+08:00]>
- 从字符串解析日期
arrow.get('2021/02/14 12:12:21','YYYY/MM/DD HH:mm:SS')
<Arrow [2021-02-14T12:12:00.210000+00:00]>
- 实例化日期
arrow.get(2021,2,14)
<Arrow [2021-02-14T00:00:00+00:00]>
- 获取当前时间
- 获取年份部分
- 获取日期部分
- 获取时间部分
- 向后偏移3周
- 明年的上月的今天
- 向前偏移1小时
- 向后偏移2小时
now = arrow.now()
now.year, now.date(), now.time(), now.shift(weeks=3), now.shift(years=1,months=-1), now.shift(hours=-1), now.shift(hours=2)
(2022, datetime.date(2022, 9, 27), datetime.time(15, 55, 43, 706035), <Arrow [2022-10-18T15:55:43.706035+08:00]>, <Arrow [2023-08-27T15:55:43.706035+08:00]>, <Arrow [2022-09-27T14:55:43.706035+08:00]>, <Arrow [2022-09-27T17:55:43.706035+08:00]>)
2. 生成时间序列
- 开始:2021-01-01,结束:2021-01-31,间隔5天
# pd.date_range(
# start=None,
# end=None,
# periods=None,
# freq=None,
# tz=None,
# normalize=False,
# name=None,
# closed=None,
# **kwargs,
# ) -> pandas.core.indexes.datetimes.DatetimeIndex
pd.date_range(start='2021-01-01',end='2021-01-31',freq='5D')
DatetimeIndex(['2021-01-01', '2021-01-06', '2021-01-11', '2021-01-16', '2021-01-21', '2021-01-26', '2021-01-31'], dtype='datetime64[ns]', freq='5D')
- 开始:2021-01-01,间隔2天,数目5个
pd.date_range(start='2021-01-01',freq='2D',periods=5)
DatetimeIndex(['2021-01-01', '2021-01-03', '2021-01-05', '2021-01-07', '2021-01-09'], dtype='datetime64[ns]', freq='2D')
- 开始:2021-01-01,结束:2021-01-31,间隔1周
pd.date_range(start='2021-01-01',end='2021-01-31',freq='W')
DatetimeIndex(['2021-01-03', '2021-01-10', '2021-01-17', '2021-01-24', '2021-01-31'], dtype='datetime64[ns]', freq='W-SUN')
- 开始:2021-01-01,间隔3小时,数目8个
pd.date_range(start='2021-01-01',freq='3H',periods=8)
DatetimeIndex(['2021-01-01 00:00:00', '2021-01-01 03:00:00', '2021-01-01 06:00:00', '2021-01-01 09:00:00', '2021-01-01 12:00:00', '2021-01-01 15:00:00', '2021-01-01 18:00:00', '2021-01-01 21:00:00'], dtype='datetime64[ns]', freq='3H')
- 开始:2021-01-01 09:00,间隔1分钟,9点开始,数目12个
pd.date_range(start='2021-01-01 09:00',freq='min',periods=12)
DatetimeIndex(['2021-01-01 09:00:00', '2021-01-01 09:01:00', '2021-01-01 09:02:00', '2021-01-01 09:03:00', '2021-01-01 09:04:00', '2021-01-01 09:05:00', '2021-01-01 09:06:00', '2021-01-01 09:07:00', '2021-01-01 09:08:00', '2021-01-01 09:09:00', '2021-01-01 09:10:00', '2021-01-01 09:11:00'], dtype='datetime64[ns]', freq='T')
- 开始:2021-01-01,结束:2021-12-31,间隔1月,月末最后1天
pd.date_range(start='2021-01-01',end='2021-12-31',freq='M')
DatetimeIndex(['2021-01-31', '2021-02-28', '2021-03-31', '2021-04-30', '2021-05-31', '2021-06-30', '2021-07-31', '2021-08-31', '2021-09-30', '2021-10-31', '2021-11-30', '2021-12-31'], dtype='datetime64[ns]', freq='M')
- 开始:2021-01-01,间隔1年,年末最后1天,数目6个
pd.date_range(start='2021-01-01',freq='Y',periods=6)
DatetimeIndex(['2021-12-31', '2022-12-31', '2023-12-31', '2024-12-31', '2025-12-31', '2026-12-31'], dtype='datetime64[ns]', freq='A-DEC')
- 开始:2021-01-01,间隔1年,年初最后1天,数目6个
pd.date_range(start='2021-01-01',freq='YS',periods=6)
DatetimeIndex(['2021-01-01', '2022-01-01', '2023-01-01', '2024-01-01', '2025-01-01', '2026-01-01'], dtype='datetime64[ns]', freq='AS-JAN')
3. 时间序列加减
df = pd.DataFrame(
data=pd.date_range('2021-01-01', freq='3D', periods=15),
columns=['日期'])
df
3.1 时间序列加减,天为单位
df['日期加十天'] = df['日期'] + pd.DateOffset(days=10)
df['日期减十天'] = df['日期'] + pd.DateOffset(days=-10)
3.2 时间序列加减,月为单位
df['日期减两月'] = df['日期'] - pd.DateOffset(months=2)
df['日期加两月'] = df['日期'] + pd.DateOffset(months=2)
3.3 时间序列加减,周为单位
df['日期减两周'] = df['日期'] - pd.DateOffset(weeks=2)
df['日期加两周'] = df['日期'] + pd.DateOffset(weeks=2)
3.4 向前(未来)偏移
- 得到最近的月末时间点
df['下期月末'] = df['日期'] + pd.offsets.MonthEnd(n=1)
3.5 向后(过去)偏移
- 得到最近的季末时间点
df['下期季度末'] = df['日期'] - pd.offsets.QuarterEnd(n=1)
df
3.6 实例化日期
pd.Timestamp('now') # pandas 获取当前时间戳
# Timestamp('2022-09-27 16:11:10.717712')
pd.Timestamp(2022,1,1,12,45,30) # 创建指定时间戳
# Timestamp('2022-01-01 12:45:30')
十、Excel多表合并
import pandas as pd
import os
# 合并demo_10文件夹下所有xlsx
path = 'data/demo_10/'
# 用于存储所有数据
df = pd.DataFrame()
for i in os.listdir(path):
# print(i)
# 完整文件路径
name = os.path.join(path,i)
# print(name)
# 单个文件的数据
data = pd.read_excel(name)
# 将单个文件的数据逐个插入
df = df.append(data)
df
十一、数据可视化
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# pyforest:懒人导包神器
1. 配置参数
# 在线作图
%matplotlib inline
# 解决中文无法显示
plt.rcParams['font.sans-serif'] = 'SimHei'
# 负号显示异常的问题
plt.rcParams['axes.unicode_minus'] = False
# 将图片格式设置为 svg 格式,提高图标质量
%config Inlinebackend.figure_format = 'svg'
2. 导入数据
df = pd.read_csv('./data/diamonds.csv')
df.head()
3. 直方图
df.hist(column='克拉',figsize=(10,10),color='#fff999')
array([[<AxesSubplot:title={'center':'克拉'}>]], dtype=object)
4. 普通箱线图
- 克拉分布
df.boxplot(column='克拉',figsize=(10,6))
5. 分组箱线图
- 各纯净度的价格分布
df.boxplot(column='价格',by='纯净度',figsize=(10,6))
<AxesSubplot:title={'center':'价格'}, xlabel='纯净度'>
- 各纯净度的克拉分布
df.boxplot(column='克拉',by='纯净度',figsize=(10,6))
<AxesSubplot:title={'center':'克拉'}, xlabel='纯净度'>
6. 核密度估计图
# plot指的是核密度图,作用类似于直方图
df['克拉'].plot(kind='kde',figsize=(10,6),xlim=(0,4))
# <AxesSubplot:ylabel='Density'>
7. 普通柱形图
- 单个维度,各纯净度的频数分布
tb = pd.crosstab(index=df['纯净度'],columns='频数')
tb.columns.name = None
tb.plot(kind='bar',figsize=(10,6))
# <AxesSubplot:xlabel='纯净度'>
8. 簇状柱形图
- 多个维度,各纯净度各颜色的频数分布
tb = pd.crosstab(index=df['纯净度'],columns=df['颜色'])
tb.plot(kind='bar',figsize=(10,6))
<AxesSubplot:xlabel='纯净度'>
9. 堆积柱形图
tb.plot(kind='bar',figsize=(10,6),stacked=True)
# <AxesSubplot:xlabel='纯净度'>
10. 散点图
df.plot(kind='scatter',x='克拉',y='价格',figsize=(10,6))
# <AxesSubplot:xlabel='克拉', ylabel='价格'>
11. 折线图
year = [str(y)+'年' for y in range(2001, 2021)]
sale = np.random.randint(100, 120, 20)
df = pd.DataFrame({'year': year, 'sale': sale})
df
df.plot(x='year',y='sale',figsize=(10,6),ylim=[0,130])
# <AxesSubplot:xlabel='year'>
12. 保存本地
plot = df.plot(x='year',y='sale',figsize=(10,6),ylim=[0,130])
plot.get_figure().savefig('折线图.png')