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行可以,因为有重复

很明显,取样的操作是针对每行的。前面我们说过,一行就是一条记录,一个样本。

posted @ 2020-04-19 09:55  如心幻雨  阅读(685)  评论(0编辑  收藏  举报