pandas快速结果处理

pandas快速结果处理

参数化DataFrame初始化

参考高维参数处理. 用itertools.product方法遍历出参数间的所有组合.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from itertools import product

import pandas as pd

parameters = [
[0.01, 0.005],
[1, 2],
[1e-9]
]
meshgrid_parameters = list(product(*parameters))
parameter_names = ['wg', 'w', 'l']

data = pd.DataFrame(columns=parameter_names, data=meshgrid_parameters)
print(data.to_markdown())
wg w l
0 0.01 1 1e-09
1 0.01 2 1e-09
2 0.005 1 1e-09
3 0.005 2 1e-09

多列遍历

这里需要讨论一下pandas里三种索引方式,一种是切片[]

1
data['wg']
wg
0 0.01
1 0.01
2 0.005
3 0.005

这种方式只能做列选取,也就是说[]内只能是列名,不支持同时在内部进行行选取.

另一种是loc,这个方法是基于行选取和标签的,支持多维筛选. 默认维度为行. 这里是因为生成DataFrame时的默认Index是数字,所以可以用.loc[0]来选取行. 如果更改了Index为字符串,那么索引就要相应做改动. (这个特性可能会引起一些问题,后面的注意问题中会提到. )

1
data.loc[0]
0
wg 0.01
w 1
l 1e-09
1
data.loc[0, 'l']

1e-09

最后一种是iloc,它和loc都是基于行选取的,不过iloc用的是基于位置的索引,不需要标签.

1
data.loc[0, 2]

1e-09

pandas里的多列索引就是从numpy的fancy index过来的,所以多列索引的基本规则和numpy基本是一样的,

1
2
3
data[['wg', 'l']]
data.loc[:, ['wg', 'l']]
data.iloc[:, [0, 2]]

这三个表达式给出的是同样的结果.

wg l
0 0.01 1e-09
1 0.01 1e-09
2 0.005 1e-09
3 0.005 1e-09

很多情况下,我们需要用某几列的值作运算,来获得新列的值,而运算中又存在无法广播的函数,这时候就需要对作多列遍历.

1
2
3
4
5
def func(wg, w):
return wg * w

for i, (wg, w) in enumerate(data[['w', 'wg']].values):
data.loc[i, 'wt'] = wg * w

当处理可广播操作的运算时,直接把列输进去就好了.

1
data['wt'] = data.wg * data.w

当然也存在一些广播函数,mapapplyapplymap,它们三个的功能都是将一个自定义函数作用到对象中的每一个元素上,它们三个的区别就是应用对象的不同. map是Series对象的函数,而applyapplymap是DataFrame对象的函数.. 比如把每个元素变10倍

1
data.applymap(lambda x: 10 * x )
wg w l
0 0.1 10 1e-08
1 0.1 20 1e-08
2 0.05 10 1e-08
3 0.05 20 1e-08

实际工作中我不是太常用到这三个函数,科学计算里主要是参数化分析,这种对某一列参数作批处理的情况不太多,处理财务、报表数据分析的工作中可能会更多出现一些.

列值筛选

一个常见的需求就是我要筛出来某个参数在特定范围下的数据,比如\(Kn_t<2\)的结果,布尔索引可以实现这个功能. 当然,也可以用python的&运算符来实现多条件筛选,实际上就是多个布尔数组做一个与运算.

1
2
index = data.w > 1.1
new_data = data[index]
wg w l wt
1 0.01 2 1e-09 0.02
3 0.005 2 1e-09 0.01

当要选取某个量为特定的几个值的数据,比如数据太密了想要挑出特定的几个点来画图,可以用pandas提供给Series对象的isin方法,这个也是从numpy迁移过来的,

1
2
index = data.w.isin([2])
new_data = data[index]
wg w l wt
1 0.01 2 1e-09 0.02
3 0.005 2 1e-09 0.01

这里要注意一个问题..当列元素是浮点数时,和浮点数的比较一样,这么筛选是非常危险的一件事. 因为计算机的舍入,浮点数的比较是没意义的(不过numpy的处理已经很周全了,很多时候np.arange生成的浮点数作比较是没问题的),后面的注意问题中讨论了一下对于浮点数情况下的处理方式.

常用的几个函数

  • .unique()

    返回某一列包含的所有的不同的值.

    1
    data.wg.unique()

    array([0.01 , 0.005])

  • .astype(np.float64)

    做一下列的类型转换.

  • .to_pickle('data.pkl')

    把DataFrame保存到文件中.

需要注意的一些问题

属性运算符与索引运算符

不能用属性运算符来增添一个新列!

1
data.new_col = x

这不会为data增添一个新列,更糟糕的是,这句话为data对象增添了一个new_col属性!这是python动态的一个特性,可以随时为对象进行属性的修改和替换. 永远不要让DataFrame的属性运算符出现在等号的左边.

关于改变DataFrame

不要用

1
data.loc[1]['xxx'] = 2

这种方式去改变DataFrame的值,因为.loc返回的已经是另一个DataFrame了,这么更改是没有作用的. 可以

1
data.loc[1, 'xxx'] = 2

布尔索引的Index

1
2
index = data.w > 1.1
new_data = data[index]
wg w l wt
1 0.01 2 1e-09 0.02
3 0.005 2 1e-09 0.01

布尔索引得到的Index并不是从0开始的,而是满足条件的原始DataFrame的Index,这里要注意. 否则可能会出现这样的问题,

1
2
3
new_data = data[index]
for i, (wg, w) in enumerate(new_data[['w', 'wg']].values):
new_data.loc[i, 'wt'] = wg * w

这里i是从0开始的,而new_data的Index却不是从0开始的,在计算的时候就会出现问题. 这里用reset_index方法重新刷新Index,

1
new_data.reset_index(drop=True, inplace=True)

危险的浮点数比较

浮点数比较是危险的. 可以用np.iscolse方法用一个相对偏差容忍度去做两个浮点数的比较,

1
np.isclose(1, 1.0000001, rtol=1e-5)

True

因此当处理浮点数列时,可以把

1
2
mask = data.w.isin([1.0, 1.1, 1.2])
new_data = data[mask]

用下面的方式来替换

1
2
mask = np.isclose(data.w.reshape(-1, 1), [1.0, 1.1, 1.2], rtol=1e-5).any(axis=1)
new_data = data[mask]

这里相当于做一个广播,最后用.any方法做一个或运算,找到在选项中的列.