【项目小结】近期pandas使用细节及技巧盘点

很少写知识点汇总的博客。主要近期实习基本上都在做这块事情,在与别人的交流中还是发现自身有相当不足,基础模块中还是有很多应当受到重视的,缺少系统的学习铺垫,很多方法就只能靠经验积累和文字记录了。


1 pandas读取数据表时对类空值字段的预处理

这种情况往往很常见,pandas在读取外部数据表文件时会对,这源于 pandas.read_table 函数的参数 na_filter , 该参数默认值为 True 即默认将类似空值的字段(如nan, null, 空字符串等等)在 DataFrame 对象中转化为 numpy.nan 的数据类型处理。

以如下数据表文件 sentences.csv 部分截图为例,注意该文件为语料数据库,不应当存在空值字段👇

import pandas as pd

df = pd.read_table("sentences.csv",header=0,sep="\t",dtype=str)

使用如上代码读取它,注意代码中虽然已经指定了 dtype=str,即以字符串格式读入所有字段的数据,但仍然会发现截图中红线部分222204条与222211条数据被记录成 NaN (numpy.float 数据类型),这就会导致在后续的字符串处理中出现问题,如果缺少经验可能很难发现是在读取文件时出现的错误,只需修正参数 na_filter=False 即可避免不必要的问题。

import pandas as pd

df = pd.read_table("sentences.csv",header=0,sep="\t",dtype=str,na_filter=False)

读入数据就不会将类似空值的字段作为 NaN 处理。


2 pandas日期格式数据处理细节及技巧

事实上pandas在读入外部数据表文件时并不会对类似日期格式的字段做转为 Datetime 格式的处理,但其实这并不影响日期格式的字段在实际使用中的问题。

以如下数据表文件 inventory.csv 部分截图为例👇

 

import pandas as pd

df = pd.read_table("inventory.csv",sep=",",header=0)

print(type(df.loc[0,"date"]))

# 输出
# <class 'str'>
# ------------------


# 做date字段的筛选

df1 = df[(df.date>="2019-02-01")&(df.date<="2019-02-28")]

print(df1.shape)

# 输出
# (15423,9)

使用上面的代码读入数据表(不指定数据格式),我们先查看date字段的数据格式,发现是字符串。但我们仍然使用date字段去做数据筛选(筛选出2019-02-01~2019-02-28的所有数据记录),发现仍然可以正常筛选。

字符串之间当然不能比较大小,但是我们仍然可以在不转化的情况下做类日期字符串之间的比较。

当然一般情况下以防万一可以对类日期格式的字段做 to_datetime 处理,如下所示👇

import pandas as pd

df = pd.read_table("inventory.csv",sep=",",header=0)

df.date = pd.to_datetime(df.date,format="%Y-%m-%d")

print(type(df.loc[0,"date"]))

# 输出
# <class 'pandas._libs.tslib.Timestamp'>
# ------------------


# 做date字段的筛选

df1 = df[(df.date>="2019-02-01")&(df.date<="2019-02-28")]

print(df1.shape)

# 输出
# (15423,9)
# ------------------

# pandas._libs.tslib.Timestamp格式字段的常用属性

df_week = df.date.dt.weekofyear # 得到该日期是当年的第几周
df_year = df.date.dt.year # 得到该日期的年号
df_weekday = df.date.dt.dayofweek # 得到该日期是星期几

# ------------------

经过这种处理后我们仍然可以使用相同的筛选方法(即与字符串作比较),并且在转化为日期格式后可以发现有很多常用的方法,如迅速调取日期的周数,年号,星期几等数据,避免了再使用datetime包的繁琐步骤。


3 关于 map 函数与 apply 函数的用法

不知道其他人是不是也有过这种奇怪的经历,以前一直把 apply 函数当作 map 函数在用,然后第一次见到 map 函数的例子后发现竟然是与apply函数同样的用法,心生困顿然后才去弄明白了apply函数的正确用法。

3.1 map 函数用法

import pandas as pd

df = pd.DataFrame({
	"A": [1,2,3,4],
	"B": [2,3,4,5],
	"C": [3,4,5,6],
})

df.A = df.A.map(lambda x: x**2) # 将A列全部平方

print(df)

# 输出

'''
    A  B  C
0   1  2  3
1   4  3  4
2   9  4  5
3  16  5  6

'''

自定义一个 DataFrame 后我们将它的A列平方处理,非常简明的用法,参数中当然可以写一个更加复杂的函数,这里是一个简单的平方函数。

近期我发现 map 函数还可以用在字段名称的映射上,比如现在我们有需求要将所有字段名称中的大写字母全部变成小写字母👇

import pandas as pd

df = pd.DataFrame({
	"A": [1,2,3,4],
	"B": [2,3,4,5],
	"C": [3,4,5,6],
})

df.columns = df.columns.map(lambda x: x.lower())

print(df)

# 输出

'''

   a  b  c
0  1  2  3
1  2  3  4
2  3  4  5
3  4  5  6

'''

这样看起来 DataFrame 格式的数据应该是在存储时将 df.columnsdf.index 都作为与 数据列 / 行 类同的数据格式处理了。

3.1 apply 函数用法

map函数不同之处在于,apply函数可以作用到多个 数据列 / 行 上,当然前面代码中的 map 函数的代码都可以被 apply 函数不作修改的直接取代,关键在于apply在对多个 数据列 / 行 的处理上的用法。

import pandas as pd

df = pd.DataFrame({
	"A": [1,2,3,4],
	"B": [2,3,4,5],
	"C": [3,4,5,6],
})

# 对同行数据做处理 axis=1

def f1(df):
	return df[0]+df[1]+df[2]

df["total"] = df[["A","B","C"]].apply(f1,axis=1)

print(df)

# 输出

'''
   A  B  C  total
0  1  2  3      6
1  2  3  4      9
2  3  4  5     12
3  4  5  6     15
'''

# -----------------------------

# 对同列数据做处理 axis=0

def f2(df):
	return df[0]+df[1]+df[2]+df[3]

total = df.apply(f2,axis=0)

print(total)

# 输出

'''
A        10
B        14
C        18
total    42
dtype: int64
'''

# -----------------------------

使用上面的代码,一般我们遇到的情况都是通过对某些字段列数据做运算后生成一列新数据,如需要对行求和,则需要指定axis=1,因为该参数默认为0,不指定则会发现 total 列全部为 NaN。如果需要对列数据求和,则指定axis=0即可。


4 杂记

  1. 在读取大文件数据读取时如果本身就已经做好了要分块处理的准备,可以在 pandas.read_table 函数中设置 chunk_size 参数,该参数默认为None,即不做分块处理,则默认将全部数据读入内存。若设置该参数为一个整型数(如16),则会将数据以类生成器的格式分为16块存储到一个类列表中(这边我的描述比较抽象,可以亲自尝试一下就会有所体会),这样做一方面读取数据极快(不论你设置 chunk_size 为多少都很快,一般来说越大越快),另一方面因为读入内存的是生成器,几乎不会占用内存,且生成器格式的数据很方便后续将数据载入模块写成生成器函数来使用(如深度学习中使用批训练方式)。另外最近python又出了一个新的数据处理包Vaex(抄袭我嵩哥[滑稽]),声称0.052秒即可读入100G的数据,其逻辑是避免将数据全部读入内存,而只是在运算时对少量数据进行抽取,再将结果返回。
  2. groupby函数确是很好用,而且感觉里面有很多方法,熟练掌握可以大大地减少代码量。当然如果记忆力不好就用下面一段代码也差不多够用了👇
    import pandas as pd
    
    data = pd.read_table("inventory.csv",header=0,sep=",")
    
    groups = data.groupby(["sku_id","plant_id"])
    
    for name,df in groups:
        print(name)
        print(df)
        # 对df做处理呗就
        # ......

    反正一个个做处理也不会很慢。

  3. 常用的表的连接:内连接与左连接的区别(数据库的东西经常就会遗忘掉)

    df = df1.merge(df2,on="group_id",how="left")  df2中找不到df1的匹配值则置空
    df = df1.merge(df2,on="group_id",how="inner")  df2中找不到df1的匹配值则删除

    (待续)


可能很多人都已经知道或者掌握笔者提到的问题,但笔者认为pandas库本身还是需要长期使用积累熟练度,身边一些朋友长时间不做数据处理回过头就会发现手生,又得花点时间从头过一遍。本文笔者可能会偶尔更新,一些简单的技巧就不多作赘述,别人都已经写过很多了。笔者主要就近期发现的问题做盘点与记录。

分享学习,共同进步!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章