Tag Archives: pandas

Hello World

pandas日期的高效处理方法

每次碰到dataframe里有datetime字段,都会感觉很麻烦,明明知道pandas里有很多批量处理的方法,但一个datetime改字符串日期,之前试过过很多写法,都不成功,又不甘心用低效的apply,于是动个小聪明,直接前置到提取数据的sql里:SUBSTR(created,1,10) day ^_^
今天又碰到这类问题,数据来源十几张表,而且是之前现成的公共模块,不好随便改sql,无奈找pandas里的处理方法,这次居然轻而易举找到了:
df[‘day’] = df[‘created’].dt.strftime(‘%Y-%m-%d’)
嗯,其实就这么简简单单一行代码~~继而好奇进一步研究了这里面的“dt”对象,除了搭配strftime()做日期格式化,还可以直接dt.year/dt.month/dt/time/dt.quarter/dt.weekday,还有dt.dayofyear/dt.weekofyear,哈哈,没错,可以理解成里面藏了一个map
除了这些,还可以类似这么用:(df[‘日期1’] – df[‘日期2’]).dt.days、(df[‘日期1’] – df[‘日期2’]).dt.total_seconds()

Hello World

dataframe拆字段

比如,数据源某字段格式:“13/34”,来表示门店每日的新老用户数,现在要把它拆成两个字段,以便下一步分析使用。当然,我们首先肯定想到可以apply,可是如果数据量太大,apply就会变得很慢。那么,有没有更快的办法?
当然有了,毕竟pandas总不会让我们失望
df[‘new’], df[‘old’] = df[‘user_count’].str.split(‘/’, 1).str
简简单单一行代码就完成了

———
over, 参考:https://zhuanlan.zhihu.com/p/343895339

Hello World

pandas之pivot_table的使用

pivot_table是将多行数据转成多列的一个函数,作用类似exce的透视表,这里咱们先看一下它的实际处理效果:

再看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#构建测试数据
df = pd.DataFrame({
	'品类': ['苹果', '橘子', '香蕉', '橘子', '香蕉', '橘子'],
	'日期': ['2023-10-21', '2023-10-21', '2023-10-21', '2023-10-22', '2023-10-23','2023-10-23'],
	'销量': [13, 24, 25, 27, 19, 21]
	},
	columns=['品类', '日期', '销量']
)
print(df)
#日期列转行 index 行索引,columns 要转为列名的行,values 要转为列值的行
df2 = pd.pivot_table(df, index=['品类'], columns=['日期'], values=['销量'])
df2.fillna(0, inplace=True)
print(df2)

———-
over,转载请注明出处:http://www.jiangkl.com/2023/10/pivot_table

Hello World

闪展腾挪,只为避开apply

apply是pandas一个强大的工具函数,可以逐行对数据进行操作。pandas的优点很多,比如代码简洁、相对直接遍历效率更高;但它的“效率高”是相对的,相对于merge之类的操作,apply的执行时间可能会高出两个数量级。比如下面这行:

1
2
  #为订单数据集增加以后一列:微信支付的金额
  df['pay_wx'] = df.apply(lambda x: (x.pay if x.chan == 'wx' else 0), axis=1)

从上面的代码看,逻辑非常简洁,但是实际测试,50万的数据量,要20秒。下面尝试改造一下

1
2
3
4
  df_wx = df[df['chan'] == 'wx']
  df_wx.rename(columns={'pay': 'pay_wx'}, inplace=True)
  df['pay_wx'] = df_wx['pay_wx']
  df.fillna({'pay_wx': 0}, inplace=True)

代码从一行变成了四行,不过执行效率可以高很多:50万数据,时间大约0.7秒
当然,方法不止一种,对于更见的判断条件,可以直接用merge操作:

1
2
3
4
5
6
7
8
9
10
  #合并某项分类依据
  df['color'] = df.apply(lambda x: ('红' if x.color == '粉红' else x.color), axis=1)
  #使用merge优化
  df_h = df[df['color'] == '粉红'][['id', 'color']]
  df_h['color'] = '红'
  df_nh = df[df['color'] != '粉红'][['id', 'color']]
  df_hs = pd.concat([df_h, df_nh])
  df_hs.rename(columns={'color':'color2'}, inplace=True)
  df = pd.merge(df, df_h, on='id', how='left')
  df['color'] = df['color2']

通过上面这种方法改造,执行时间和第一个方法接近,50万条数据大约0.8秒

———–
转载请注明出处:http://www.jiangkl.com/2022/12/no_apply

Hello World

pandas降低内存占用小技巧

这些年个人电脑的配置逐渐提高,服务器的配置反而停滞不前,自己手头的笔记本早就16G内存了,可公司用的阿里云的服务器,8G内存已经算“较高配置”了。。。
作为php、mysql之类的服务器,8G确实够用了,可做pandas数据分析,就不一定了,这不现在要做一个月度数据分析,要加载一个月的订单量,大概五千万量级,在本地虽然慢点,但结果都能跑出来,可到了服务器上,虽然linux开了虚拟内存,可不但比本地慢很多,还经常跑到半截被“killed”掉了。。。
“你被终结了,蠢货”,每到这个时候,就想起了星际里雷神的这句台词 O(∩_∩)O哈哈~
本地跑的时候,大概会占用12G内存,精简了不必要的字段以后,降低到了9G,还是不行,于是开始招其他能够降低内存占用的方法,比较有效的是修改数据类型,不过试了几次,int64改int16、int32,都没有效果,只有下面这个,让内存占从9G降低到了7、8G:
df[‘hour’] = df[‘hour’].astype(‘category’)
hour字段原本是字符串类型,直接.info(),显示是object,变成category以后,确实能节约不少空间
—————–先写到这里,后续验证有效的能减少内存占用的方法会持续更新(2021年12月21日17:07:05)

Hello World

Pandas双轴折线图生产与中文显示

双轴折线图,适合显示有两组不同类型,或者幅度差异较大的数据,这里展示的是一个对pandas生产双轴折线图的封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
# figsize 输出尺寸,默认1000×300
# savePath 保存路径
# title 折线图上显示的标题
# rightColumns 放在右侧的字段
# leftLabel  rightLabel  左右标签
def buildChartLine(dataDf, savePath, xInd='日期', figsize=(10, 3), title='', rightColumns=[], leftLabel='', rightLabel='', colors=[]):
    if isLocalServ(): # macos本地的中文配置
        plt.rcParams['font.sans-serif'] = ['SimHei']
        plt.rcParams['axes.unicode_minus'] = False
    else: # Linux的中文配置
        tfont = FontProperties(fname=r"/usr/share/fonts/simhei.ttf", size=14)
    dataDf.sort_values(xInd, inplace=True) # 确保顺序
    dataDf = dataDf.copy()
    dataDf.set_index(xInd, inplace=True) # 确保索引x轴字段名
    if len(colors) == 0:
        #TODO: 自定义颜色策略
        print('no color')
    plt.figure()
    if isLocalServ(): # macos本地的中文配置,和下面else里的,主要还是中文配置的差异,其他相同
        if len(rightColumns) > 0:
            ax = dataDf.plot(secondary_y=rightColumns, figsize=figsize, color=colors, kind='line')
            ax.right_ax.set_ylabel(rightLabel)
        else:
            ax = dataDf.plot(figsize=figsize, color=colors, kind='line')
        ax.set_ylabel(leftLabel)
        if len(title) > 0: ax.set(title=title)
        ax.legend(loc=2)
        plt.legend(loc=1)
    else:
        if len(rightColumns) > 0:
            ax = dataDf.plot(secondary_y=rightColumns, figsize=figsize, color=colors, kind='line')
            ax.right_ax.set_ylabel(rightLabel, fontproperties=tfont)
        else:
            ax = dataDf.plot(figsize=figsize, color=colors, kind='line')
        ax.set_ylabel(leftLabel, fontproperties=tfont)
        if len(title) > 0: plt.title(title, fontproperties=tfont)
        ax.legend(loc=2, prop=tfont)
        plt.legend(loc=1, prop=tfont)
    # plt.show() #直接显示折线图
    plt.savefig(savePath)

使用示例

1
2
3
4
5
6
7
dataDf = pd.DataFrame({
	'日期': ['2021-11-01', '2021-11-02', '2021-11-04',  '2021-11-06',  '2021-11-03',  '2021-11-05'],
	'风力': [3, 4, 5, 7, 9,	1],
	'最高温度': [13, 24, 25, 27, 19, 21],
	'最低温度': [1, 4, 5, 7, 9, 2]},
	columns=['日期', '风力', '最高温度', '最低温度'])
buildChartLine(dataDf, 'dayInfo', xInd='日期', figsize=(10, 3), title='气象记录', rightColumns=['风力'], leftLabel='温度(℃)', rightLabel='风力(级)', colors=['#fb0320', '#e50ce2', '#770ce5'])

实际效果:

小结:搞定这个双轴折线图过程中,主要碰到了两个问题:
1. pandas plot网上资料很多,但可能是因为版本的问题,大部分直接拿过来用,是用不了的,最终拼凑出上面的方法
2. linux里中文的解决。本来想让运维协助配置linux本地字库,无奈运维搞了半天也没效果,最终只能写死字库位置这个方案;另外一个比较坑的地方,同样是字体的定义,label/title那里参数名叫fontproperties,到了legend又叫prop。。。
—–
over
转载请注明出处:http://www.jiangkl.com/2021/12/pandas_iine_font/

Hello World

dataframe根据给定list排序

pandas自带的sort_values已经算是足够强大了,能够满足大部分需求,但是偶然,会有定制排序的需求,比如在输出数据表格的时候,想要把重点项目放在最前面。下面的方法,介绍了一种排序思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
#构建测试数据
df1 = pd.DataFrame({
	'uid': ['小A',  		'小B',  		'小A',  		'小A',  		'小A',  		'小B',  		  '小B',  	     '小A',  		'小B',  	   '小A'],
	'day': ['2021-09-25',  	'2021-09-25',  '2021-09-25',  	'2021-09-26',  '2021-09-26',  '2021-09-26',  	'2021-09-26', '2021-09-27',  '2021-09-25',  '2021-09-25'],
	'sub': ['语文', 		'语文', 		'数学', 		'语文', 		 '数学', 		  '语文',           '数学',        '语文',         '英语',        '英语'],
	'val': [95,             95,            74,             87,             65,             77,              66,            32 ,           55,            90]}, 
		columns=['uid', 'day', 'sub', 'val'])
sortList = ['数学', '语文', '英语']  #排序依据数组,依据sub,科目
# df1.reset_index(inplace=True) #视数据实际情况,排序字段不能是索引
df1['sub'] = df1['sub'].astype('category') #重新设置字段类型,为排序做准备
df1['sub'].cat.set_categories(sortList, inplace=True) #设置排序依据
df1.sort_values('sub', inplace=True) #排序操作
print(df1)

输出:
———–
uid day sub val
2 小A 2021-09-25 数学 74
4 小A 2021-09-26 数学 65
6 小B 2021-09-26 数学 66
0 小A 2021-09-25 语文 95
1 小B 2021-09-25 语文 95
3 小A 2021-09-26 语文 87
5 小B 2021-09-26 语文 77
7 小A 2021-09-27 语文 32
8 小B 2021-09-25 英语 55
9 小A 2021-09-25 英语 90
———–
如上,既完成了根据sortList预定义顺序的排序操作
不过有个地方需要注意:如果sub列里包含sortList没有的值,这里的输出结果可能会出现未知异常
——
over
转载请注明出处: http://www.jiangkl.com/2021/10/dataframe_sort_list

Hello World

dataframe快速转dict

习惯了新瓶装旧酒,pandas各种数据结构虽然很好用,可以有时候还是喜欢熟悉的json。不过python自带的dataframe转json方法并不好用,比如datetime格式就会转不过去。可实际上,大部分时间不用管这个点,因为转json,只是为了打到日志里,看着方便。。。所以,转dict就可以。这里就用到了panda自带的to_dict()方法
比如有下面这样一个dataframe,df1:

现在想将每个城市的平均气温打印出来,就可以这么操作:
str(df1.set_index(‘城市’)[‘平均温度’].to_dict())
输出:{“平均温度”:{“北京”:19, “上海”:23, “深圳”:29}}
——
over

Hello World

使用pandas+openpyxl生成多子表excel

虽然python+pandas自身有强大的数据分析和展现能力,可实际工作中,不是每个人都会coding的,所以,有时候把粗加工的数据,以excel的形式提供数据,会更灵活、更适合大部分,这里介绍一种可以生成多子表excel的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import pandas as pd
from openpyxl import Workbook
from openpyxl.reader.excel import load_workbook
 
#数据暂存容器
xlsDatas = {}
#保存路径
savePath = '.../test-excel.xlsx'
#获取数据
df1 = pd.DataFrame(
    {
        '日期': ['2021-01-01', '2021-01-02', '2021-01-03', '2021-01-04'], 
        '订单数': [1234, 1321, 3456, 6543],
        '用户数': [234, 787, 987, 545]},
    columns=['日期', '订单数', '用户数']
)
xlsDatas['概况'] = df1  #子表名-子表数据
df2 = pd.DataFrame(
    [
        [56335, '兰州拉面', 15.0, '2021-01-01 12:12:12'], 
        [45135, '山西削面', 13.0, '2021-01-01 12:16:12'], 
        [16335, '兰州拉面', 15.0, '2021-01-01 12:32:12']
    ],
    index=list('123'),
    columns=['用户id', '产品名称', '消费金额', '消费日期']
)
xlsDatas['订单列表'] = df2
#使用Workbook保存数据
book0 = Workbook()
book0.save(savePath) #先保存一个空excel文件,然后往里塞子表,实际会有一个空的Sheet子表
book = load_workbook(savePath)
writer = pd.ExcelWriter(savePath, engine='openpyxl') #选择openpyxl引擎
writer.book = book
for k in xlsDatas :
    xlsDatas[k].to_excel(writer, sheet_name=k)
writer.save()

————
over
转载请注明出处:http://www.jiangkl.com/2021/08/pandas_openpyxl_excel

Hello World

apply与lambda简单搭配,一行代码实现按条件补齐数据

需求:订单列表里的点位信息,只有点位编号,没有点位场景类型,需要补齐这个数据,以便实现按场景分析订单数据

1
2
3
4
5
  orderDf = pd.read_csv('path...')  #订单数据
  nodePlaces = ... #查库,返回 点位编号 - 场景类型 字典数据
  orderDf['place'] = orderDf.apply(lambda x: nodePlaces[x.nodeId] if x.nodeId in nodePlaces else 0, axis = 1) //#补齐场景类型字段place
  print(orderDf.colums)  #查库补齐效果
  print(orderDf.tail(5))

实际测试,orderDf七千万行,nodePlaces五万个点位,添加place这一行,执行时间约为1分钟
over
—–
转载请注明出处:http://www.jiangkl.com/2021/07/pandas_apply_lambda
—–
两周后补充:
这个需求,还有一个效率更高的方法,就是使用pd.merge()
已上面的例子继续折腾:

1
2
3
4
5
6
  orderDf = pd.read_csv('path...')  #订单数据
  nodePlaces = ... #查库,返回 点位编号 - 场景类型 字典数据
  nodeDf = pd.DataFrame(list(nodePlaces()), columns=['nodeId', 'place']) #转成datafram
  orderDf = pd.merge(orderDf, nodeDf, on='nodeId', how='left') //#补齐场景类型字段place
  print(orderDf.colums)  #查库补齐效果
  print(orderDf.tail(5))

我没实际在使用上面的例子做测试,而是再另一个场景下使用了这种方法,实际对比,原来apply使用200秒的一个需求,换成这里的merge以后,降到了700毫秒,降了三个数量级!
不过这个方法也有个缺陷,那就是,如果nodePlaces里没有这个nodeId,orderDf的place字段,会被设置为NaN,解决的办法有两个:
1. 使用fillna替换
2. 构建nodeDf的时候,将nodePlaces里没有的nodeId也补进去