pandas 轴索引的重命名,离散化,异常值的处理与随机方法
一、重命名轴索引
与Series对象类似,轴索引也有一个map方法:
In [83]: df = pd.DataFrame(np.arange(12).reshape((3, 4)),
...: index=['Ohio', 'Colorado', 'New York'],
...: columns=['one', 'two', 'three', 'four'])
...:
In [84]: transform = lambda x: x[:4].upper() # 截取前4个字符并大写
In [85]: df.index.map(transform) # map的结果
Out[85]: Index(['OHIO', 'COLO', 'NEW '], dtype='object')
In [86]: df.index = df.index.map(transform) #用结果修改原来的index
In [87]: df
Out[87]:
one two three four
OHIO 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
还可以使用rename方法修改索引,且不修改原数据:
# 参数的值是对索引进行修改的处理函数,比如str.title
In [88]: df.rename(index=str.title, columns=str.upper)
Out[88]:
ONE TWO THREE FOUR
Ohio 0 1 2 3
Colo 4 5 6 7
New 8 9 10 11
In [89]: df # 原值未变
Out[89]:
one two three four
OHIO 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
或者使用字典的方式,将指定的索引重命名为新值:
In [90]: df.rename(index={'OHIO': 'INDIANA'},
...: columns={'three': 'peekaboo'})
...:
Out[90]:
one two peekaboo four
INDIANA 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
In [91]: df
Out[91]:
one two three four
OHIO 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
照样可以使用inplace=True修改原数据集。
二、离散化
离散化,就是将连续值转换为一个个区间内,形成一个个分隔的‘箱子’。假设我们有下面的一群人的年龄数据,想将它们进行分组,并放入离散的年龄箱内:
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
我们预先定义18~25、26~35、36~60以及61及以上等若干组。
Pandas提供一个cut方法,帮助我们实现分箱功能:
In [93]: bins = [18,25,35,60,100]
In [94]: cats = pd.cut(ages,bins)
In [95]: cats
Out[95]:
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
返回的cats是一个特殊的Categorical对象,输出描述了12个年龄值分别处于哪个箱子中。cats包含一系列的属性:
In [96]: cats.codes
Out[96]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
In [97]: cats.categories
Out[97]:
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
closed='right',
dtype='interval[int64]')
In [98]: cats.describe
Out[98]:
<bound method Categorical.describe of [(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]>
In [99]: pd.value_counts(cats) # 各个箱子的数量
Out[99]:
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
dtype: int64
分箱的区间通常是左开右闭的,如果想变成左闭右开,请设置参数right=False。
可以定义labels参数,来自定义每种箱子的名称:
In [100]: group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']^M
...: pd.cut(ages, bins, labels=group_names)
Out[100]:
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior]
如果你不提供分箱的区间定义,而是直接要求分隔成整数个等分区间,可以这么做:
In [101]: d =np.random.rand(20)
In [102]: d
Out[102]:
array([0.83732945, 0.0850416 , 0.66540597, 0.90479238, 0.99222014,
0.39409122, 0.91896172, 0.87163655, 0.31374598, 0.27726111,
0.7716572 , 0.79131961, 0.42805445, 0.29934685, 0.19077374,
0.79701771, 0.93789892, 0.93536338, 0.32299602, 0.305671 ])
In [103]: pd.cut(d, 4, precision=2) # 精度限制在两位
Out[103]:
[(0.77, 0.99], (0.084, 0.31], (0.54, 0.77], (0.77, 0.99], (0.77, 0.99], ..., (0.77, 0.99], (0.77, 0.99], (0.77, 0.99], (0.31, 0.54], (0.084, 0.31]]
Length: 20
Categories (4, interval[float64]): [(0.084, 0.31] < (0.31, 0.54] < (0.54, 0.77] < (0.77, 0.99]]
cut函数执行的时候,分箱区间要么是你指定的,要么是均匀大小的。还有一种分箱方法叫做qcut,它是使用样本的分位数来分割的,而不是样本值的大小。比如下面的操作,将使每个箱子中元素的个数相等:
In [104]: data = np.random.randn(1000)
In [105]: cats = pd.qcut(data,4)
In [106]: cats
Out[106]:
[(0.644, 2.83], (-0.0344, 0.644], (-0.0344, 0.644], (-0.734, -0.0344], (-0.734, -0.0344], ..., (-3.327, -0.734], (-0.734, -0.0344], (0.644, 2.83], (-0.734, -0.0344], (-0.0344, 0.644]]
Length: 1000
Categories (4, interval[float64]): [(-3.327, -0.734] < (-0.734, -0.0344] < (-0.0344, 0.644] < (0.644, 2.83]]
In [108]: pd.value_counts(cats) # 各箱子中的元素个数相同
Out[108]:
(0.644, 2.83] 250
(-0.0344, 0.644] 250
(-0.734, -0.0344] 250
(-3.327, -0.734] 250
dtype: int64
qcut还可以自定义0~1之间的分位数:
pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.])
离散化函数对于分位数和分组分析特别有用。
三、异常值处理
检测和过滤异常值是数据清洗过程中非常重要的一步。比如你手里有个一万成人的身高数据,其中有几个3m以上,1m以下的数据,这都是属于异常值,需要被过滤掉。
我们来看下面的一组正态分布的数据:
In [109]: df = pd.DataFrame(np.random.randn(1000, 4))
In [110]: df.describe()
Out[110]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean -0.001764 0.023018 0.038956 0.016735
std 0.998835 1.041113 1.025342 1.019077
min -3.035805 -3.056706 -2.857154 -2.922755
25% -0.654426 -0.695994 -0.712627 -0.702766
50% -0.005015 0.011862 0.020562 -0.041231
75% 0.660182 0.683478 0.732590 0.758038
max 3.323073 3.701470 3.280375 3.997876
如果你想找出第二列数据中绝对值大于3的元素:
In [113]: col = df[2]
In [114]: col[np.abs(col) > 3]
Out[114]:
30 3.091161
113 3.280375
Name: 2, dtype: float64
如果要选出所有行内有值大于3或小于-3的行,可以使用下面的any方法搭配:
In [125]: df[(np.abs(df)>3).any(1)]
Out[125]:
0 1 2 3
28 0.008674 0.046048 -0.171580 3.997876
30 0.709758 -1.871982 3.091161 -0.819429
113 0.432223 -0.675313 3.280375 0.841355
169 3.323073 -0.608988 0.685795 -0.710693
177 -1.514524 -3.056706 -0.760937 1.300434
322 3.296765 0.971996 0.114804 1.855576
410 3.246140 -0.039501 1.530122 1.502243
496 -3.035805 -0.535662 0.703911 0.916483
575 -0.127245 3.701470 -0.642512 0.281001
720 3.045646 1.266809 1.263198 1.429049
799 0.523183 -0.246954 1.132868 3.141117
首先,我们对整个df取绝对值,然后和3比较,形成一个bool的DataFrame,再使用any在行方向(参数1的作用)进行判断是否有True的存在,如果有,则保存在一个Series中,最后,用这个Series作为行号取df取出对应的行。
我们还可以将绝对值大于3的数分别设置为+3和-3,只需要使用np.sign(x)函数,这个函数根据x的符号分别生成+1和-1:
In [127]: df[np.abs(df) > 3] = np.sign(df) * 3
In [130]: df.describe()
Out[130]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean -0.002640 0.022374 0.038584 0.015596
std 0.995851 1.038699 1.024224 1.015232
min -3.000000 -3.000000 -2.857154 -2.922755
25% -0.654426 -0.695994 -0.712627 -0.702766
50% -0.005015 0.011862 0.020562 -0.041231
75% 0.660182 0.683478 0.732590 0.758038
max 3.000000 3.000000 3.000000 3.000000
In [133]: (np.sign(df)*3).head()
Out[133]:
0 1 2 3
0 3.0 3.0 -3.0 -3.0
1 3.0 3.0 -3.0 -3.0
2 -3.0 -3.0 -3.0 -3.0
3 -3.0 -3.0 3.0 -3.0
4 3.0 -3.0 3.0 -3.0
四、随机抽样
有时候,我们需要打乱原有的数据顺序,让数据看起来像现实中比较混沌、自然的样子。这里推荐一个permutation操作,它来自numpy.random,可以随机生成一个序列:
In [134]: order = np.random.permutation(5) # 5个数
In [135]: order
Out[135]: array([3, 4, 1, 2, 0])
然后我们用它处理下面的df,让行的顺序变成和order的一样:
In [136]: df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))
In [137]: df
Out[137]:
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
4 16 17 18 19
In [138]: df.take(order) #
Out[138]:
0 1 2 3
3 12 13 14 15
4 16 17 18 19
1 4 5 6 7
2 8 9 10 11
0 0 1 2 3
In [139]: df.iloc[order] # 同上
Out[139]:
0 1 2 3
3 12 13 14 15
4 16 17 18 19
1 4 5 6 7
2 8 9 10 11
0 0 1 2 3
可以看到,通过take函数,使用order作为参数,打乱了df的行顺序。
还有一种叫做抽样的操作,从原样本集合中抽取一部分形成新的样本集合,分重复抽样和不重复抽样。pandas提供的sample函数帮我们实现了这一功能:
In [140]: df.sample(n=3)
Out[140]:
0 1 2 3
0 0 1 2 3
4 16 17 18 19
1 4 5 6 7
In [141]: df.sample(n=10)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
In [142]: df.sample(n=10,replace=True)
Out[142]:
0 1 2 3
3 12 13 14 15
2 8 9 10 11
2 8 9 10 11
0 0 1 2 3
3 12 13 14 15
0 0 1 2 3
2 8 9 10 11
4 16 17 18 19
1 4 5 6 7
3 12 13 14 15
- n=3:指定从原来数据中抽取3行
- n=10:弹出异常,因为原数据不够10行
- replace=True:可以重复抽取,这样10行可以,因为有重复
很明显,取样的操作是针对每行的。前面我们说过,一行就是一条记录,一个样本。