【python】pandas模块


想法

日常刷刷知乎,看到这个问题:pandas 在使用时语法感觉很乱,有什么学习的技巧吗?

想来自己也经常使用pandas,但是每次使用都会忘记一些函数、方法,而且df太多,各种变量名层出不穷,不会管理内存,导致表一多,容易爆…下面我们看看知乎大佬是如何使用pandas的?

原文:https://www.zhihu.com/question/289788451/answer/2495499460


提示:以下是本篇文章正文内容,下面案例可供参考

1.临时DataFrame散落在一个notebook各处,(下文缩写为df)

为了帮菜鸟debug一个error经常要trace一个又一个临时DataFrame溯源出错的列到底是怎么来的,df1,df2,df3…, df_temp1, df_temp2等等,真的心累。

还有,许多人为了看了一些网络教程,为避免满屏的setting with copy warning的,建的都是深度拷贝(df.copy(deep=True)。极大浪费了内存。题主所谓的语法乱,应该很大程度上是这样造成的。

可以用pipe方法解决这个问题,pipe即为管道,把前一项输出的DF,作为后一项输入的DF,同时把df操作函数对象作为第一数,它所需的参args和kwargs传入。这样避免产生中间的df。当参数复杂(比如是巨大的dictionary,或者是一连串函数计算后的结果)、高阶方法多,比直接chaining可读性高。

# 举个例子,每次分析工作完成后,把琐碎的数据清理工作以如下形式放在数据导入后的下一步
dtype_mapping = {'a':np.float32, 'b':np.uint8, 'c':np.float64, 'd':np.int64, 'e':str}
df_cleaned = (df
  .pipe(pd.DataFrame.sort_index, ascending=False) #按索引排序
  .pipe(pd.DataFrame.fillna,value=0, method='ffill') #缺失值处理
  .pipe(pd.DataFrame.astype, dtype_mapping) #数据类型变换
  .pipe(pd.DataFrame.clip, lower= -0.05, upper=0.05) #极端值处理
)  
# 也可以包装成一个函数
def clean_data(df):
  ...#上面的pipe操作
  return df_cleaned

2 衍生列、辅助列生生成在各个角落

在这里插入图片描述

这会导致debug困难,尤其是列还是前后依赖的情况。通常还伴随着setting with copy warning。可以使用assign方法,把一些列生成操作集中在一起。(和直接用df['x] = … 不同的是assign方法会生成一个新的df,原始的df不会变 ,不会有setting with copy warning),还有一个好处,就是不会因为生成新的操作而打断函数chaining.

# 官方doc的例子
df = pd.DataFrame(data=25 + 5 * np.random.randn(10), columns=["temp_c"])
df.assign(temp_f=lambda x: x['temp_c'] * 9 / 5 + 32,
          temp_k=lambda x: (x['temp_f'] +  459.67) * 5 / 9)

3. 多个简单条件组合起来的筛选看上去很复杂

用query解决很多条件的问题筛选的问题。

df = pd.DataFrame(data=np.random.randn(10,3), columns=list("abc"))

#用query
df.query("(a>0 and b<0.05) or c>b")
#普通方法
df.loc[((df['a']>0) & (df['b']<0.05))| (df['c']>df['b'])]

明显query方法简洁,而且条件越多,逻辑判断越多,可读性优势就越明显(前提是单个条件都是简单的判断)。

4.不必要的iloc或者iterrow或者itertuple遍历df

凡是数值操作,用pandas或者numpy原生的函数一般比你自己定义一个函数要快1个数量级以上,而且可读性完全不一样。以算股票收益率为例。

作者:peter
链接:https://www.zhihu.com/question/289788451/answer/2495499460
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

# 以下是数据准备。
import pandas as pd
import numpy as np
import pandas_datareader as web
import datetime

start = datetime.datetime(2021, 6, 1)
end = datetime.datetime(2021, 12, 31)

#选google,testla,neflix,和coke
assets = ['GOOG', 'TSLA', 'NFLX', 'KO']

#读取4个股票在2021年下半年的历史交易数据
df = web.DataReader(assets, 'stooq', start, end) 、
df_cls_price = df.loc[:,'Close'] #只看收盘价

下面是错误的示范,没有耐心的同学可以直接跳过。

#方法一用iloc遍历的方式
def wrong_func():
    df_wrong = pd.DataFrame(index=df_cls_price.index, 
        columns=df_cls_price.columns)

    for i in range(df_cls_price.shape[0]):
        for s in assets:
            if i == 0:
                df_wrong.iloc[i][s] = 0
            else:
                #通过iloc[i-1]和iloc[i]做差
                diff = (df_cls_price.iloc[i][s] - df_cls_price.iloc[i-1][s])
                denominator = df_cls_price.iloc[i-1][s]
                df_wrong.iloc[i][s] = diff/denominator
    return df_wrong



#call这个上述方法
wrong_func()

#方法二、用pandas自带方法
 df_cls_price.pct_change()

#看下执行效率

在这里插入图片描述
在我的机器上时间差了200多倍,而且方便好多。

5 把timeseries数据当成string操作,又慢又难懂

# 还是上面的例子,求股票月度平均价格

# 方法一、用groupby,string来做
(df_cls_price
   # 用function作为grouper时,会取日期索引字符串前7位,比如2021-07
  .groupby(lambda x: str(x)[:7]) 
  .mean()#方法二、用resample来操作
(df_cls_price
  .resample('1M')
  .mean()
)

方法2更直观且速度快,而且可复用性变强了,可以随时换到其他时间区间,方法一就不行了。

# 方法二,也可以很容易扩展到其他时间区间
(df_cls_price
.resample('10T') #10天
#.resample('2W')  #双周 
#.resample('Q)    #季度
.mean()
)
# 还可以有分钟、秒,不适用本案例

6.不必要的merge

常见情况是用了汇总操作,然后把汇总结果merge回原来的数据。然后进行下一步计算。这就可以用transform代替。接上例,这次做一个原价减去月度均价的操作。

#方法一、用agg汇总后再merge到原表
df_wrong = df_cls_price.reset_index() #把datetime64的索引变成列,列名为Date
df_wrong['month'] = df_wrong['Date'].apply(lambda x: str(x)[:7]) # 生成month辅助列

#得到月均价
df_wrong_avgprice = (df_wrong
 .groupby('month')
 .mean()
)

#把月均价df和原来数据合并
df_wrong_joined = df_wrong.join(df_wrong_avgprice,on='month', rsuffix='_1m_mean')

#计算
df_wrong_joined.assign(
    GOOG_demean = df_wrong_joined['GOOG'] - df_wrong_joined['GOOG_1m_mean'],
    TSLA_demean = df_wrong_joined['TSLA'] - df_wrong_joined['TSLA_1m_mean'],
    NFLX_demean = df_wrong_joined['NFLX'] - df_wrong_joined['NFLX_1m_mean'],
    KO_demean = df_wrong_joined['KO'] - df_wrong_joined['KO_1m_mean']
)

#方法二、用grouper加transform
df_cls_price.groupby(pd.Grouper(freq='1M')).transform(lambda x: x- x.mean())

#方法三、熟练用户会直接用‘-’,更快更简洁
df_cls_price - df_cls_price.groupby(pd.Grouper(freq='1M')).transform(np.mean)

#看一下效率

在这里插入图片描述
可以看到用transform明显代码简洁,而且没有生成必要的df和不必要的辅助列。而且可以非常容易扩展到其他时间间隔。

7.没有向量化的思维,太多for循环,不会用numpy造作

参考这个回答。这是个典型的利用numpy广播机制,比较列和行的问题。
如何用 pandas 处理这个问题?

8.apply函数用非常复杂的条件,很多的if else

def abcd_to_e(x):
    if x['a']>1:
        return 1
    elif x['b']<0:
        return x['b']
    elif x['a']>0.1 and x['b']<10:
        return 10
    elif ...:
        return 1000
    elif ...
    
    else: 
        return x['c']

df.apply(abcd_to_e, axis=1)

这个用numpy的select可以避免,这个方法的好处是可以给定任意条件,并匹配对应的值。满足条件1,赋值v1;满足条件2,赋值v2。。。如果条件多了,也能一次完成赋值操作。

np.select(condlist=[con1, con2, con3, ...], choicelist=[v1, v2, v3, ...], other=0)
df.assign(c=np.select([(df.a>0.5)&(df.b<0.9), df.a<0, df.b<0],
                      [df.b, 0, -np.inf], 
                      default=np.nan))

更重要的是select方法比用apply一个复杂的if else自定义函数效率要快几个数量级

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值