移除重复数据
DataFrame中出现重复行有多种原因。下面就是一个例子
In [43]: data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'],...: 'k2': [1,1,2,3,3,4,4]})In [44]: dataOut[44]:k1 k20 one 11 two 12 one 23 two 34 one 35 two 46 two 4
DataFrame的duplicated 方法返回一个布尔型Series,表示各行是否是重复行(前面出现过的行)
In [45]: data.duplicated()Out[45]:0 False1 False2 False3 False4 False5 False6 Truedtype: bool
还有一个与此相关的drop_duplicates 方法,它会返回一个DataFrame,重复的数组会标为False
In [46]: data.drop_duplicates()Out[46]:k1 k20 one 11 two 12 one 23 two 34 one 35 two 4
这两个方法默认会判断全部列,你也可以指定部分列进行重复项判断。假设我们还有一列值,且只希望根据k1 列过滤重复项
In [47]: data['v1'] = range(7)In [48]: data.drop_duplicates(['k1'])Out[48]:k1 k2 v10 one 1 01 two 1 1
duplicated 和drop_duplicates 默认保留的是第一个出现的值组合。传入keep='last' 则保留最后一个
In [49]: data.drop_duplicates(['k1', 'k2'], keep='last')Out[49]:k1 k2 v10 one 1 01 two 1 12 one 2 23 two 3 34 one 3 46 two 4 6
利用函数或映射进行数据转换
对于许多数据集,你可能希望根据数组、Series或DataFrame列中的值来实现转换工作。我们来看看下面这组有关肉类的数据
In [2]: data = pd.DataFrame({'food': ['bacon', 'pulled pork', 'bacon',...: 'Pastrami', 'corned beef', 'Bacon',...: 'pastrami', 'honey ham', 'nova lox'],...: 'ounces': [4,3,12,6,7.5,8,3,5,6]})In [3]: dataOut[3]:food ounces0 bacon 4.01 pulled pork 3.02 bacon 12.03 Pastrami 6.04 corned beef 7.55 Bacon 8.06 pastrami 3.07 honey ham 5.08 nova lox 6.0
假设你想要添加一列表示该肉类食物来源的动物类型。我们先编写一个不同肉类到动物的映射
In [4]: meat_to_animal = {...: 'bacon': 'pig',...: 'pulled pork': 'pig',...: 'pastrami': 'cow',...: 'corned beef': 'cow',...: 'honey ham': 'pig',...: 'nova lox': 'salmon'}
Series的 map 方法可以接受一个函数或含有映射关系的字典型对象,但是这里有一个小问题,即有些肉类的首字母大写了,而另一些则没有。因此,我们还需要使用Series的str.lower 方法,将各个值转换为小写
In [5]: lowercased = data['food'].str.lower()In [6]: lowercasedOut[6]:0 bacon1 pulled pork2 bacon3 pastrami4 corned beef5 bacon6 pastrami7 honey ham8 nova loxName: food, dtype: objectIn [7]: data['animal'] = lowercased.map(meat_to_animal)In [8]: dataOut[8]:food ounces animal0 bacon 4.0 pig1 pulled pork 3.0 pig2 bacon 12.0 pig3 Pastrami 6.0 cow4 corned beef 7.5 cow5 Bacon 8.0 pig6 pastrami 3.0 cow7 honey ham 5.0 pig8 nova lox 6.0 salmon
我们也可以传入一个能够完成全部这些工作的函数
In [9]: data['food'].map(lambda x: meat_to_animal[x.lower()])Out[9]:0 pig1 pig2 pig3 cow4 cow5 pig6 cow7 pig8 salmonName: food, dtype: object
使用map 是一种实现元素级转换以及其他数据清理工作的便捷方式
替换值
利用fillna 方法填充缺失数据可以看做值替换的一种特殊情况。前面已经看到,map 可用于修改对象的数据子集,而replace 则提供了一种实现该功能的更简单、更灵活的方式。我们来看看下面这个Series
In [13]: data = pd.Series([1., -999., 2., -999., -1000., 3.])In [14]: dataOut[14]:0 1.01 -999.02 2.03 -999.04 -1000.05 3.0dtype: float64
-999这个值可能是一个表示缺失数据的标记值。要将其替换为pandas能够理解的NA值,我们可以利用replace 来产生一个新的Series(除非传入inplace=True )
In [17]: data.replace(-999, np.nan)Out[17]:0 1.01 NaN2 2.03 NaN4 -1000.05 3.0dtype: float64
如果你希望一次性替换多个值,可以传入一个由待替换值组成的列表以及一个替换值
In [18]: data.replace([-999, -1000], np.nan)Out[18]:0 1.01 NaN2 2.03 NaN4 NaN5 3.0dtype: float64
要让每个值有不同的替换值,可以传递一个替换列表
In [19]: data.replace([-999, -1000], [np.nan, 0])Out[19]:0 1.01 NaN2 2.03 NaN4 0.05 3.0dtype: float64
传入的参数也可以是字典
In [20]: data.replace({-999: np.nan, -1000: 0})Out[20]:0 1.01 NaN2 2.03 NaN4 0.05 3.0dtype: float64
data.replace方法与data.str.replace不同, 后者做的是字符串的元素级替换。
重命名索引
跟Series中的值一样,轴标签也可以通过函数或映射进行转换,从而得到一个新的不同标签的对象。轴还可以被就地修改,而无需新建一个数据结构。接下来看看下面这个简单的例子
In [22]: transform = lambda x: x[:4].upper()In [23]: data.index.map(transform)Out[23]: Index(['OHIO', 'COLO', 'NEW '], dtype='object')
你可以将其赋值给 index ,这样就可以对DataFrame进行就地修改
In [24]: data.index = data.index.map(transform)In [25]: dataOut[25]:one two three fourOHIO 0 1 2 3COLO 4 5 6 7NEW 8 9 10 11
特别说明一下,rename 可以结合字典型对象实现对部分轴标签的更新
In [27]: data.rename(index={'OHIO': 'INDIANA'},...: columns={'three': 'peekaboo'})Out[27]:one two peekaboo fourINDIANA 0 1 2 3COLO 4 5 6 7NEW 8 9 10 11
rename 可以实现复制DataFrame并对其索引和列标签进行赋值。如果希望就地修改某个数据集,传入inplace=True 即可
In [28]: data.rename(index={'OHIO': 'INDIANA'}, inplace=True)In [29]: dataOut[29]:one two three fourINDIANA 0 1 2 3COLO 4 5 6 7NEW 8 9 10 11
离散化和面元划分
为了便于分析,连续数据常常被离散化或拆分为“面元”(bin)假设有一组人员数据,而你希望将它们划分为不同的年龄组
In [30]: ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41 ,32]In [31]: bins = [18, 25, 35, 60, 100]In [32]: cats = pd.cut(ages, bins)In [33]: catsOut[33]:[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]Length: 12Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
pandas返回的是一个特殊的Categorical对象。结果展示了pandas.cut 划分的面元。你可以将其看做一组表示面元名称的字符串。 它的底层含有一个表示不同分类名称的类型数组, 以及一个codes 属性中的年龄数据的标签
In [34]: cats.codesOut[34]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)In [35]: cats.categoriesOut[35]:IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]],closed='right',dtype='interval[int64]')In [36]: pd.value_counts(cats)Out[36]:(18, 25] 5(35, 60] 3(25, 35] 3(60, 100] 1dtype: int64
pd.value_counts(cats) 是 pandas.cut 结果的面元计数
跟“区间”的数学符号一样,圆括号表示开端,而方括号则表示闭端(包括)。哪边是闭端可以通right=False 进行修改
In [37]: pd.cut(ages, [18, 26, 36, 61, 100], right=False)Out[37]:[[18, 26), [18, 26), [18, 26), [26, 36), [18, 26), ..., [26, 36), [61, 100), [36, 61), [36, 61), [26, 36)]Length: 12Categories (4, interval[int64]): [[18, 26) < [26, 36) < [36, 61) < [61, 100)]
你可以通过传递一个列表或数组到lables ,设置自己的面元名称
In [38]: group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Seniro']In [39]: pd.cut(ages, bins, labels=group_names)Out[39]:[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Seniro, MiddleAged, MiddleAged, YoungAdult]Length: 12Categories (4, object): [Youth < YoungAdult < MiddleAged < Seniro]
如果向cut 传入的是面元的数量而不是确切的面元边界, 则它会根据数据的最小值和最大值计算等长面元。 下面这个例子中, 我们将一些均匀分布的数据分成四组
In [40]: data = np.random.rand(20)In [41]: pd.cut(data, 4, precision=2)Out[41]:[(0.76, 0.99], (0.76, 0.99], (0.53, 0.76], (0.067, 0.3], (0.53, 0.76], ..., (0.3, 0.53], (0.067, 0.3], (0.76, 0.99], (0.76, 0.99], (0.76, 0.99]]Length: 20Categories (4, interval[float64]): [(0.067, 0.3] < (0.3, 0.53] < (0.53, 0.76] < (0.76, 0.99]]
选项 precision=2 限定小数点的位数qcut 是一个非常类似于cut 的函数,它可以根据样本分位数对数据进行面元划分。根据数据的分布情况,cut 可能无法使各个面元中含有相同数量的数据点。 而qcut由于使用的是样本分位数, 因此可以得到大小基本相等的面元
In [42]: data = np.random.rand(1000)In [43]: cats = pd.qcut(data, 4)In [44]: catsOut[44]:[(0.496, 0.742], (0.003, 0.233], (0.233, 0.496], (0.233, 0.496], (0.003, 0.233], ..., (0.496, 0.742], (0.496, 0.742], (0.233, 0.496], (0.233, 0.496], (0.233, 0.496]]Length: 1000Categories (4, interval[float64]): [(0.003, 0.233] < (0.233, 0.496] < (0.496, 0.742] < (0.742, 0.999]]In [45]: pd.value_counts(cats)Out[45]:(0.742, 0.999] 250(0.496, 0.742] 250(0.233, 0.496] 250(0.003, 0.233] 250dtype: int64
与cut 类似,你也可以传递自定义的分位数(0到1之间的数值,包含端点)
In [46]: pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.])Out[46]:[(0.496, 0.9], (0.003, 0.0805], (0.0805, 0.496], (0.0805, 0.496], (0.0805, 0.496], ..., (0.496, 0.9], (0.496, 0.9], (0.0805, 0.496], (0.0805, 0.496], (0.0805, 0.496]]Length: 1000Categories (4, interval[float64]): [(0.003, 0.0805] < (0.0805, 0.496] < (0.496, 0.9] < (0.9, 0.999]]
检测和过滤异常值
过滤或变换异常值(outlier) 在很大程度上就是运用数组运算
In [3]: data = pd.DataFrame(np.random.randn(1000, 4))In [4]: data.describe()Out[4]:0 1 2 3count 1000.000000 1000.000000 1000.000000 1000.000000mean 0.033135 0.032570 -0.095518 -0.017979std 0.986617 0.977234 1.024877 1.020564min -2.914406 -2.664110 -3.506253 -3.94089925% -0.644735 -0.640543 -0.804517 -0.72960150% 0.000484 0.061080 -0.124947 0.03113875% 0.716358 0.665481 0.557026 0.625428max 3.112172 3.163979 3.495929 3.114999
假设你想要找出某列中绝对值大小超过3的值
In [5]: col = data[2]In [6]: col[np.abs(col) > 3]Out[6]:34 3.49592998 3.315447579 -3.007528825 -3.186834893 -3.506253Name: 2, dtype: float64
要选出全部含有“超过3或-3的值”的行,你可以在布尔型DataFrame中使用 any 方法
In [7]: data[(np.abs(data) > 3).any(1)]Out[7]:0 1 2 334 -1.784538 0.445514 3.495929 1.82988643 3.112172 0.411621 1.468454 -1.65296584 0.503056 3.038186 -1.127855 0.40090798 -0.863137 1.232352 3.315447 2.187843122 0.378206 3.163979 -2.019452 0.313086253 1.968076 -1.584542 0.401178 -3.050984403 -0.861885 3.022449 -0.494384 0.895285414 -0.757196 0.155083 -1.797796 -3.022605579 1.167474 0.243371 -3.007528 -1.252314593 3.073241 1.438949 -0.865515 -1.809210646 -0.950297 1.460927 -1.230396 -3.940899773 -1.075818 -0.408849 -0.375849 3.114999825 0.703684 0.260653 -3.186834 0.859110893 -0.213223 -0.880939 -3.506253 0.726953915 1.940736 0.464242 -0.003480 -3.296612
根据这些条件,就可以对值进行设置。下面的代码可以将值限制在区间-3到3以内
In [9]: data[np.abs(data) > 3] = np.sign(data) * 3In [10]: data.describe()Out[10]:0 1 2 3count 1000.000000 1000.000000 1000.000000 1000.000000mean 0.032949 0.032346 -0.095629 -0.016783std 0.986050 0.976536 1.020114 1.015892min -2.914406 -2.664110 -3.000000 -3.00000025% -0.644735 -0.640543 -0.804517 -0.72960150% 0.000484 0.061080 -0.124947 0.03113875% 0.716358 0.665481 0.557026 0.625428max 3.000000 3.000000 3.000000 3.000000
根据数据的值是正还是负,np.sign(data) 可以生成1和-1
In [11]: np.sign(data).head()Out[11]:0 1 2 30 -1.0 1.0 -1.0 -1.01 -1.0 1.0 -1.0 1.02 -1.0 -1.0 -1.0 -1.03 -1.0 -1.0 1.0 1.04 -1.0 1.0 1.0 1.0
排列和随机采样
利用numpy.random.permutation 函数可以轻松实现对Series或DataFrame的列的排列工作(permuting, 随机重排序)。通过需要排列的轴的长度调用permutation ,可产生一个表示新顺序的整数数组
In [12]: df = pd.DataFrame(np.arange(5 * 4).reshape((5,4)))In [13]: sampler = np.random.permutation(5)In [14]: samplerOut[14]: array([4, 0, 2, 1, 3])
然后就可以在基于iloc 的索引操作或take 函数中使用该数组了
In [15]: dfOut[15]:0 1 2 30 0 1 2 31 4 5 6 72 8 9 10 113 12 13 14 154 16 17 18 19In [16]: df.take(sampler)Out[16]:0 1 2 34 16 17 18 190 0 1 2 32 8 9 10 111 4 5 6 73 12 13 14 15
如果不想用替换的方式选取随机子集,可以在Series和DataFrame上使用sample 方法
In [17]: df.sample(n=3)Out[17]:0 1 2 33 12 13 14 152 8 9 10 114 16 17 18 19
要通过替换的方式产生样本(允许重复选择),可以传递replace=True 到sample
In [18]: choices = pd.Series([5, 7, -1, 6, 4])In [19]: draws = choices.sample(n=10, replace=True)In [20]: drawsOut[20]:1 71 70 54 42 -13 61 72 -14 40 5dtype: int64
