#!/usr/bin/env python # coding: utf-8 # # 7.2 Data Transformation(数据变换) # # # 1 删除重复值 # # # In[1]: import pandas as pd import numpy as np # In[2]: data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]}) data # DataFrame方法duplicated返回的是一个boolean Series,表示一个row是否是重复的(根据前一行来判断): # In[3]: data.duplicated() # drop_duplicateds返回一个DataFrame,会删除重复的部分: # In[4]: data.drop_duplicates() # 上面两种方法都默认考虑所有列;另外,我们可以指定一部分来检测重复值。假设我们只想检测'k1'列的重复值: # In[7]: data['v1'] = range(7) data # In[6]: data.drop_duplicates(['k1']) # duplicated和drop_duplicated默认保留第一次观测到的数值组合。设置`keep='last'`能返回最后一个: # In[8]: data.drop_duplicates(['k1', 'k2'], keep='last') # # 2 Transforming Data Using a Function or Mapping(用函数和映射来转换数据) # # 有时候我们可能希望做一些数据转换。比如下面一个例子,有不同种类的肉: # In[14]: 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]}) data # 假设你想加一列,表明每种肉来源的动物是什么。我们可以写一个映射: # In[18]: meat_to_animal = { 'bacon': 'pig', 'pulled pork': 'pig', 'pastrami': 'cow', 'corned beef': 'cow', 'honey ham': 'pig', 'nova lox': 'salmon' } # 用于series的map方法接受一个函数,或是一个字典,包含着映射关系,但这里有一个小问题,有些肉是大写,有些是小写。因此,我们先用str.lower把所有的值变为小写: # In[19]: lowercased = data['food'].str.lower() lowercased # In[20]: data['animal'] = lowercased.map(meat_to_animal) data # 我们也可以用一个函数解决上面的问题: # In[21]: data['food'].map(lambda x: meat_to_animal[x.lower()]) # 使用map是一个很简便的方法,用于element-wise转换和其他一些数据清洗操作。 # # # 3 Replacing Values(替换值) # # 其实fillna是一个特殊换的替换操作。map可以用于修改一个object里的部分值,但是replace能提供一个更简单和更灵活的方法做到这点。下面是一个series: # In[22]: data = pd.Series([1., -999., 2., -999., -1000., 3.]) data # 这里-999可能是用来表示缺失值的标识符。用NA来替代的话,用replace,会产生一个新series(除非使用inplace=True): # In[23]: data.replace(-999, np.nan) # 如果想要一次替换多个值,直接用一个list即可: # In[25]: data.replace([-999, -1000], np.nan) # 对于不同的值用不同的替换值,也是导入一个list: # In[26]: data.replace([-999, -1000], [np.nan, 0]) # 参数也可以是一个dict: # In[27]: data.replace({-999: np.nan, -1000: 0}) # 注意:data.replace方法和data.str.replace方法是不同的,后者会对string进行element-wise替换。 # # # 4 Renaming Axis Indexes(重命名Axis Indexes) # # # 像是series里的value一样,axis label也能类似地是函数或映射来转换,产生一个新的object。当然也可以设置in-place不产生新的数据: # In[29]: data = pd.DataFrame(np.arange(12).reshape((3, 4)), index=['Ohio', 'Colorado', 'New York'], columns=['one', 'two', 'three', 'four']) data # 与series相同,axis index有一个map方法: # In[30]: transform = lambda x: x[:4].upper() transform # In[32]: data.index # In[31]: data.index.map(transform) # 可以赋值给index,以in-place的方式修改DataFrame: # In[33]: data.index = data.index.map(transform) data # 如果你想要创建一个转换后的版本,而且不用修改原始的数据,可以用rename: # In[34]: data.rename(index=str.title, columns=str.upper) # 注意,rename能用于dict一样的oject, # In[35]: data.rename(index={'OHIO': 'INDIANA'}, columns={'three': 'pekaboo'}) # rename能让你避免陷入手动赋值给index和columns的杂务中。可以用inplace直接修改原始数据: # In[36]: data.rename(index={'OHIO': 'INDIANA'}, inplace=True) data # # 5 Discretization and Binning(离散化和装箱) # # # 连续型数据经常被离散化或分散成bins(分箱)来分析。假设你有一组数据,你想把人分到不同的年龄组里: # In[37]: ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32] # 我们把这些分到四个bin里,19~25, 26~35, 36~60, >60。可以用pandas里的cut: # In[38]: bins = [18, 25, 35, 60, 100] cats = pd.cut(ages, bins) cats # 返回的是一个特殊的Categorical object。我们看到的结果描述了pandas.cut如何得到bins。可以看作是一个string数组用来表示bin的名字,它内部包含了一个categories数组,用来记录不同类别的名字,并伴有表示ages的label(可以通过codes属性查看): # In[39]: cats.codes # In[40]: cats.categories # In[41]: pd.value_counts(cats) # 这里pd.value_counts(cats)是pandas.cut后bin的数量。 # # 这里我们注意一下区间。括号表示不包含,方括号表示包含。你可以自己设定哪一边关闭(right=False): # In[42]: pd.cut(ages, [18, 26, 36, 61, 100], right=False) # 你也可以用一个list或数组给labels选项来设定bin的名字: # In[43]: group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior'] # In[44]: pd.cut(ages, bins, labels=group_names) # 如果你只是给一个bins的数量来cut,而不是自己设定每个bind的范围,cut会根据最大值和最小值来计算等长的bins。比如下面我们想要做一个均匀分布的四个bins: # In[45]: data = np.random.rand(20) # In[46]: pd.cut(data, 4, precision=2) # precision=2选项表示精确到小数点后两位。 # # 一个近似的函数,qcut,会按照数据的分位数来分箱。取决于数据的分布,用cut通常不能保证每一个bin有一个相同数量的数据点。而qcut是按百分比来切的,所以可以得到等数量的bins: # In[47]: data = np.random.randn(1000) # Normally distributed # In[48]: cats = pd.qcut(data, 4) # Cut into quartiles cats # In[49]: pd.value_counts(cats) # 类似的,在cut中我们可以自己指定百分比: # In[51]: cats2 = pd.cut(data, [0, 0.1, 0.5, 0.9, 1.]) # 累进的百分比 cats2 # In[52]: pd.value_counts(cats2) # 在之后的章节我们还会用到cut和qcut,这些离散函数对于量化和群聚分析很有用。 # # # 6 Detecting and Filtering Outliers(检测和过滤异常值) # # 过滤或转换异常值是数组操作的一个重头戏。下面的DataFrame有正态分布的数据: # In[53]: data = pd.DataFrame(np.random.randn(1000, 4)) data.describe() # 假设我们想要找一个列中,绝对值大于3的数字: # In[54]: data.head() # In[56]: col = data[2] col.head() # In[57]: col[np.abs(col) > 3] # 选中所有绝对值大于3的行,可以用any方法在一个boolean DataFrame上: # In[60]: data[(np.abs(data) > 3)].head() # In[62]: data[(np.abs(data) > 3).any(1)] # any中axis=1表示column # 下面是把绝对值大于3的数字直接变成-3或3: # In[64]: data[np.abs(data) > 3] = np.sign(data) * 3 # In[67]: data[21:23] # In[68]: data.describe() # np.sign(data)会根据值的正负号来得到1或-1: # In[69]: np.sign(data).head() # # 7 Permutation and Random Sampling(排列和随机采样) # # 排列(随机排序)一个series或DataFrame中的row,用numpy.random.permutation函数很容易就能做到。调用permutation的时候设定好你想要进行排列的axis,会产生一个整数数组表示新的顺序: # In[70]: df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4))) df # In[72]: sampler = np.random.permutation(5) sampler # 这个数组能被用在基于iloc上的indexing或take函数: # In[73]: df.take(sampler) # 为了选中一个随机的子集,而且没有代替功能(既不影响原来的值,返回一个新的series或DataFrame),可以用sample方法: # In[75]: df.sample(n=3) # 如果想要生成的样本带有替代功能(即允许重复),给sample中设定replace=True: # In[77]: choices = pd.Series([5, 7, -1, 6, 4]) draws = choices.sample(n=10, replace=True) draws # # 8 Computing Indicator/Dummy Variables(计算指示器/虚拟变量) # # > Dummy Variables:虚拟变量,又称虚设变量、名义变量或哑变量,用以反映质的属性的一个人工变量,是量化了的自变量,通常取值为0或1。 # # 另一种在统计模型上的转换或机器学习应用是把一个categorical variable(类别变量)变为一个dummy or indicator matrix(虚拟或指示器矩阵)。如果DataFrame中的一列有k个不同的值,我们可以用一个矩阵或DataFrame用k列来表示,1或0。pandas有一个get_dummies函数实现这个工作,当然,你自己设计一个其实也不难。这里举个例子: # In[79]: df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'], 'data1': range(6)}) df # In[80]: pd.get_dummies(df['key']) # 在一些情况里,如果我们想要给column加一个prefix, 可以用data.get_dummies里的prefix参数来实现: # In[81]: dummies = pd.get_dummies(df['key'], prefix='key') # In[82]: df_with_dummy = df[['data1']].join(dummies) df_with_dummy # 如果DataFrame中的a row属于多个类别,事情会变得复杂一些。我们来看一下MoviesLens 1M 数据集: # In[83]: mnames = ['movie_id', 'title', 'genres'] # In[85]: movies = pd.read_table('../datasets/movielens/movies.dat', sep='::', header=None, names=mnames, engine='python') movies[:10] # 给每个genre添加一个指示变量比较麻烦。首先我们先取出所有不同的类别: # In[86]: all_genres = [] for x in movies.genres: all_genres.extend(x.split('|')) genres = pd.unique(all_genres) genres # 一种构建indicator dataframe的方法是先构建一个全是0的DataFrame: # In[88]: zero_matrix = np.zeros((len(movies), len(genres))) zero_matrix.shape # In[90]: dummies = pd.DataFrame(zero_matrix, columns=genres) dummies.head() # 然后迭代每一部movie,并设置每一行中的dummies为1。使用dummies.columns来计算每一列的genre的指示器: # In[91]: gen = movies.genres[0] gen.split('|') # In[92]: dummies.columns.get_indexer(gen.split('|')) # 然后,使用.iloc,根据索引来设定值: # In[93]: for i, gen in enumerate(movies.genres): indices = dummies.columns.get_indexer(gen.split('|')) dummies.iloc[i, indices] = 1 # In[96]: dummies.head() # 然后,我们可以结合这个和movies: # In[94]: movies_windic = movies.join(dummies.add_prefix('Genre_')) movies_windic.iloc[0] # 对于一个很大的数据集,这种构建多个成员指示变量的方法并不会加快速度。写一个低层级的函数来直接写一个numpy array,并把写过整合到DataFrame会更快一些。 # # 一个有用的recipe诀窍是把get_dummies和离散函数(比如cut)结合起来: # In[97]: np.random.seed(12345) # In[98]: values = np.random.rand(10) values # In[99]: bins = [0, 0.2, 0.4, 0.6, 0.8, 1.] # In[101]: pd.cut(values, bins) # In[100]: pd.get_dummies(pd.cut(values, bins))