《利用python进行数据分析》读书笔记--第七章 数据规整化:清理、转换、合并、重塑(二)

http://www.cnblogs.com/batteryhp/p/5040342.html

 

 

3、数据转换

介绍完数据的重排之后,下面介绍数据的过滤、清理、以及其他转换工作。

  • 去重
复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame

#DataFrame去重
data = DataFrame({'k1':['one']*3 + ['two'] * 4,
    'k2':[1,1,2,3,3,4,4,]})
#print data
print data.duplicated() #返回一个布尔型Series,重复的为True,不重复的为False
#得到去重之后的DataFrame,应该意识到这是非常常用的
print data.drop_duplicates().reset_index(drop = True)
#可以选定需要去重的列
print data.drop_duplicates(['k1']) #默认保留第一次出现的行
print data.drop_duplicates(['k1'],take_last = True) #设定保留最后一个出现的行
>>>
0    False
1     True
2    False
3    False
4     True
5    False
6     True
    k1  k2
0  one   1
1  one   2
2  two   3
3  two   4
    k1  k2
0  one   1
3  two   3
    k1  k2
2  one   2
6  two   4
[Finished in 0.7s]
复制代码
  • 利用函数或者映射进行数据转换
复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame

data = 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]})
print data
#假如你想添加一列表示该肉类食物来源的动物类型,我们先编写一个肉类到动物的映射。
meat_to_animal = {
    'bacon':'pig',
    'pulled pork':'pig',
    'pastrami':'cow',
    'corned beef':'cow',
    'honey ham':'pig',
    'nova lox':'salmon'
}
#Series的map方法可以接受一个函数或含有映射关系的字典型对象,但是这里有个问题:有些大写了,
#有些没有。因此需要先转换大小写(注意数据清理过程),感觉这方法很实用
data['animal'] = data['food'].map(str.lower).map(meat_to_animal)
print data
#下面看一下map用来执行函数,即将data['food']的每个元素应用到隐含函数
print data['food'].map(lambda x:meat_to_animal[x.lower()])
>>>
          food  ounces
0        bacon     4.0
1  pulled pork     3.0
2        bacon    12.0
3     Pastrami     6.0
4  corned beef     7.5
5        Bacon     8.0
6     pastrami     3.0
7    honey ham     5.0
8     nova lox     6.0
          food  ounces  animal
0        bacon     4.0     pig
1  pulled pork     3.0     pig
2        bacon    12.0     pig
3     Pastrami     6.0     cow
4  corned beef     7.5     cow
5        Bacon     8.0     pig
6     pastrami     3.0     cow
7    honey ham     5.0     pig
8     nova lox     6.0  salmon
0       pig
1       pig
2       pig
3       cow
4       cow
5       pig
6       cow
7       pig
8    salmon
Name: food
[Finished in 0.8s]
复制代码
  • 替换值
复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame

#下面看replace函数
data = Series([1.,-999.,2.,-999.,-1000.,3.])
print data
#用replace替换-999、-1000,注意Series可以直接用,相当于矢量化了
print data.replace([-999,-1000],np.nan)
#下面看一下numpy,不能直接用replace和map
#data1 = np.arange(10)
#print data1.replace(0,np.nan)
#print data1.map(lambda x: x + 1)
print data.replace([-999,-1000],[np.nan,0])
print data.replace({-999:np.nan,-1000:0})
>>>
0       1
1    -999
2       2
3    -999
4   -1000
5       3
0     1
1   NaN
2     2
3   NaN
4   NaN
5     3
0     1
1   NaN
2     2
3   NaN
4     0
5     3
0     1
1   NaN
2     2
3   NaN
4     0
5     3
[Finished in 0.8s]
复制代码
  • 重命名轴索引

跟Series的值一样,轴标签可以通过函数或者映射进行转换,从而得到一个新对象,轴还可以被就地修改,而无需新建一个数据结构。

复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame

data = DataFrame(np.arange(12).reshape((3,4)),index = ['Ohio','Colorado','New York'],
    columns = ['one','two','three','four'])
print data
#轴标签的map方法
print data.index.map(str.upper)
#就地修改
data.index = data.index.map(str.upper)
print data
#下面用rename得到一个副本
print data.rename(index = str.title,columns = str.upper)
#rename可以结合字典对象进行更新
print data.rename(index = {'OHIO':'INDIANA'},columns = {'three':'peekaboo'})
#rename可以将DataFrame的索引和标签进行复制和赋值
#就地修改
_ = data.rename(index = {'OHIO':'INDIANA'},inplace = True)
print data
print '\n',type(_)
print _
>>>
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
New York    8    9     10    11
[OHIO COLORADO NEW YORK]
          one  two  three  four
OHIO        0    1      2     3
COLORADO    4    5      6     7
NEW YORK    8    9     10    11
          ONE  TWO  THREE  FOUR
Ohio        0    1      2     3
Colorado    4    5      6     7
New York    8    9     10    11
          one  two  peekaboo  four
INDIANA     0    1         2     3
COLORADO    4    5         6     7
NEW YORK    8    9        10    11
          one  two  three  four
INDIANA     0    1      2     3
COLORADO    4    5      6     7
NEW YORK    8    9     10    11

<class 'pandas.core.frame.DataFrame'>
          one  two  three  four
INDIANA     0    1      2     3
COLORADO    4    5      6     7
NEW YORK    8    9     10    11
[Finished in 0.8s]

复制代码
  • 离散化和面元划分

为了便于分析,连续数据常常被离散化或拆分为面元(bin),即分组。

复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame

ages = [20,22,25,27,21,23,37,31,61,45,41,32]
bins = [18,25,35,60,100]
#用的是cut函数
cats = pd.cut(ages,bins)
print cats
#返回的是一个特殊的Categorical对象,可以看作是表示面元名称的字符串。
#它含有一个表示不同分类名称的levels数组以及一个labels属性:
print cats.labels  #是分组的序号,标示为第几组
print cats.levels
print pd.value_counts(cats)
#得到的是几个“区间”,不包括左,包括右,可用right = False包括左,不包括右
print pd.cut(ages,[18,26,36,61,100],right = False)
#可以设置自己的面元名称,设置label是即可
group_names = ['Youth','YoungAdult','MiddleAged','Senior']
print pd.cut(ages,bins,labels = group_names)
#当然可以为cut传入面元的数量而不是具体的分界点,会自动均匀分布
data = np.random.randn(20)
print data
#下面标识分为4组,精度为2位
print pd.cut(data,4,precision = 2)
#qcut函数是一个类似于cut的函数,可以根据样本分位数对数据进行面元划分。根据数据,cut可能无法
#是各个面元数量数据点相同,qcut使用的是样本分位数,因此可以得大小基本相等的面元。
data = np.random.randn(1000)
cats = pd.qcut(data,4) #四份位数进行切割
print cats
print pd.value_counts(cats)
#当然可以设置自定义的分位数(0到1的值)
print pd.qcut(data,[0,0.1,0.5,0.9,1.])
>>>
Categorical: 
array([(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], (18, 25],
       (35, 60], (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]], dtype=object)
Levels (4): Index([(18, 25], (25, 35], (35, 60], (60, 100]], dtype=object)
[0 0 0 1 0 0 2 1 3 2 2 1]
array([(18, 25], (25, 35], (35, 60], (60, 100]], dtype=object)
(18, 25]     5
(35, 60]     3
(25, 35]     3
(60, 100]    1
Categorical:
array([[18, 26), [18, 26), [18, 26), [26, 36), [18, 26), [18, 26),
       [36, 61), [26, 36), [61, 100), [36, 61), [36, 61), [26, 36)], dtype=object)
Levels (4): Index([[18, 26), [26, 36), [36, 61), [61, 100)], dtype=object)
Categorical:
array([Youth, Youth, Youth, YoungAdult, Youth, Youth, MiddleAged,
       YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult], dtype=object)
Levels (4): Index([Youth, YoungAdult, MiddleAged, Senior], dtype=object)
Categorical:
array([(-0.5, 0.66], (0.66, 1.82], (0.66, 1.82], (-0.5, 0.66],
       (-1.67, -0.5], (0.66, 1.82], (-0.5, 0.66], (-1.67, -0.5],
       (0.66, 1.82], (-1.67, -0.5], (-1.67, -0.5], (-1.67, -0.5],
       (-1.67, -0.5], (-0.5, 0.66], (-0.5, 0.66], (-0.5, 0.66],
       (-0.5, 0.66], (1.82, 2.98], (-0.5, 0.66], (-0.5, 0.66]], dtype=object)
Levels (4): Index([(-1.67, -0.5], (-0.5, 0.66], (0.66, 1.82],
                   (1.82, 2.98]], dtype=object)
[-3.161, -0.624]    250
(0.69, 2.982]       250
(0.0578, 0.69]      250
(-0.624, 0.0578]    250
[Finished in 0.7s]
复制代码
  • 检测和过滤异常值

异常值(outlier)的过滤或变换运算在很大程度上其实就是数组运算。

复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame

np.random.seed(12345)
data = DataFrame(np.random.randn(1000,4))
print data.describe()
#假设想要找出某些列中绝对值大小超过3的值
col = data[3]
#print col
print col[np.abs(col) > 3]
#找出全部含有超过3或-3的值的行
print data[(np.abs(data) > 3).any(1)]
#对上面的这样的值限制在-3到3内
data[np.abs(data) > 3] = np.sign(data) * 3
print data.describe()
>>>
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.067684     0.067924     0.025598    -0.002298
std       0.998035     0.992106     1.006835     0.996794
min      -3.428254    -3.548824    -3.184377    -3.745356
25%      -0.774890    -0.591841    -0.641675    -0.644144
50%      -0.116401     0.101143     0.002073    -0.013611
75%       0.616366     0.780282     0.680391     0.654328
max       3.366626     2.653656     3.260383     3.927528
97     3.927528
305   -3.399312
400   -3.745356
Name: 3
            0         1         2         3
5   -0.539741  0.476985  3.248944 -1.021228
97  -0.774363  0.552936  0.106061  3.927528
102 -0.655054 -0.565230  3.176873  0.959533
305 -2.315555  0.457246 -0.025907 -3.399312
324  0.050188  1.951312  3.260383  0.963301
400  0.146326  0.508391 -0.196713 -3.745356
499 -0.293333 -0.242459 -3.056990  1.918403
523 -3.428254 -0.296336 -0.439938 -0.867165
586  0.275144  1.179227 -3.184377  1.369891
808 -0.362528 -3.548824  1.553205 -2.186301
900  3.366626 -2.372214  0.851010  1.332846
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.067623     0.068473     0.025153    -0.002081
std       0.995485     0.990253     1.003977     0.989736
min      -3.000000    -3.000000    -3.000000    -3.000000
25%      -0.774890    -0.591841    -0.641675    -0.644144
50%      -0.116401     0.101143     0.002073    -0.013611
75%       0.616366     0.780282     0.680391     0.654328
max       3.000000     2.653656     3.000000     3.000000
[Finished in 0.8s]
复制代码
  • 排列和随机采样

下面是随机选取一个DataFrame的一些行,做法就是随机产生行号,然后进行选取即可。

复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame

df = DataFrame(np.arange(5 * 4).reshape(5,4))
sampler = np.random.permutation(5)  #返回一个随机排列
print df
print sampler
#然后可以在基于ix的索引操作或者take函数中使用该数组
print df.take(sampler)
#作者这里说了非替换式采样,我理解就是不重复采样吧!
#下面是进行截取
print df.take(np.random.permutation(len(df))[:3])
bag = np.array([5,7,-1,6,4])
sampler = np.random.randint(0,len(bag),size = 10)
print sampler
draws = bag.take(sampler)
print draws
>>>
    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
[3 2 1 0 4]
    0   1   2   3
3  12  13  14  15
2   8   9  10  11
1   4   5   6   7
0   0   1   2   3
4  16  17  18  19
    0   1   2   3
4  16  17  18  19
0   0   1   2   3
3  12  13  14  15
[1 0 1 3 4 3 3 2 0 2]
[ 7  5  7  6  4  6  6 -1  5 -1]
[Finished in 0.7s]
复制代码
  • 计算指标/哑变量

另一种常用的用于统计建模或机器学习的转换方式是:将分类变量(categorical variable)转换为“哑变量矩阵”(dummy matrix)或“指标矩阵”(indicator matrix)。如果DataFrame的某一列有k各不同的值,可以派生出一个k列的矩阵或者DataFrame(值为1和0)。这样的做法在下一章(第八章)的地图的例子中有体现(谁让我先看的第八章,当时还在想这个办法好,原来根源在这里)。

复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame

df = DataFrame({'key':['b','b','a','c','a','b'],'data1' : range(6)})
print df
print pd.get_dummies(df['key'])  #得到哑变量DataFrame
#有时候,你想给指标DataFrame的列加上一个前缀,一边进行合并。
#这个功能好,但是注意是给指标DataFrame的列名加的前缀
dummies = pd.get_dummies(df['key'],prefix = 'key')
print dummies
df_with_dummy = df[['data1']].join(dummies)  #按行索引合并
print df_with_dummy
#这里说一个隐藏的trick,df['data1']得到一个Series,而df[['data1']]得到一个DataFrame
print type(df['data1'])    #Series而已,列名丢掉
print type(df[['data1']])  #DataFrame 是有列名的

#下面看如果DataFrame的某行属于多个分类怎么办,利用ch02中的MovieLens数据。
names = ['movie_id','title','genres']
movies = pd.read_table('E:\\movies.dat',sep = '::',header = None,names = names)
print movies[:10]
#要为genre添加指标变量的时候需要先进性数据规整。
#首先把所有genres提取出来
genre_iter = (set(x.split('|')) for x in movies.genres)
genres = sorted(set.union(*genre_iter))
dummies = DataFrame(np.zeros((len(movies),len(genres))),columns = genres)
#接下来,迭代每一部电影并将dummies各行的项设置为1
for i,gen in enumerate(movies.genres):
    dummies.ix[i,gen.split('|')] = 1
#然后与movies合并起来
movies_windic = movies.join(dummies.add_prefix('Genre_'))
print movies_windic.ix[0]
#但是对于河大的数据,这种方法构建指标非常慢。肯定需要编写一个能够利用DataFrame内部机制的更低级的函数才行
#一个对统计应用的秘诀是:结合get_dummies和诸如cut之类的离散化函数
values = np.random.rand(10)
print values
bins = [0,0.2,0.4,0.6,0.8,1]
print pd.get_dummies(pd.cut(values,bins))
复制代码

4、字符串操作

Python有简单易用的字符串和文本处理功能。大部分文本运算直接做成了字符串对象的内置方法。当然还能用正则表达式。pandas对此进行了加强,能够对数组数据应用字符串表达式和正则表达式,而且能处理烦人的缺失数据。

  • 字符串对象方法
复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame

#字符串对象方法
#对于大部分的字符串而言,内置的方法已经能够满足要求了
val = 'a,b, guido'
print val.split(',')  #返回的是一个列表
pieces = [x.strip() for x in val.split(',')]  #strip函数修剪空白字符
print pieces
#利用加法可以把字符串连接起来,注意下面的赋值方式
first,second,third = pieces
print first +'::' + second +'::'+ third
#上面的不实用,下面是一种更快的风格
print '::'.join(pieces)
#另一种方法是字串定位,常用的有 in、index、find
print 'guido' in val  #返回布尔型,是否在字符串中
print val.index(',')  #返回第一次出现的位置,找不到返回异常
print val.find(':')   #返回第一次出现字符的位置,找不到返回-1,可以指定从哪个位置开始和结束
print val.count(',')  #返回个数
print val.replace(',','::')
print val.replace(',','') #传入''用来删除字符 
>>>
['a', 'b', ' guido']
['a', 'b', 'guido']
a::b::guido
a::b::guido
True
1
-1
2
a::b:: guido
ab guido
[Finished in 0.6s]
#上面这些都能用正则表达式实现
复制代码

Python内置的字符串方法有:

image

image

  • 正则表达式

正则表达式(regex)提供了一种灵活的在文本中搜索、匹配字符串的模式。用的是re模块。re模块的函数分为3类:模式匹配、替换、拆分。关于正则表达式的总结,参考一下:http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html (谢谢作者)。

复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame
import re

text = "foo  bar\t baz  \tqux"
print re.split('\s+',text) #这条语句是先编译正则表达式 \s+ (多个空白字符),然后再调用split
regex = re.compile('\s+')
print regex.split(text)
#下面是找到匹配regex的所有模式
print regex.findall(text)
#注意:想转义字符\不起作用,即作为一个单独字符,可以直接在前面加r,原生字符串
text1 = r'foo \t'
print text1
#如果想对许多字符串都应用同一条正则表达式,应该先compile节省时间
#findall 返回字符串中所有匹配项,而search则只返回第一个匹配项。match更加严格,它只匹配字符串的首部
text = """Dave dave@google.com
Steve steve@gmail.com
Rob rob@gmail.com
Ryan ryan@yahoo.com
"""
pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'
#下面第二个参数的作用是正则对大小写不敏感
regex = re.compile(pattern,flags = re.IGNORECASE)
print regex.findall(text)  #返回一个list
#search返回第一个邮件地址,返回的是一种特殊特殊对象,这个对象只能告诉我们模式在原始字符串中的起始和结束位置
m = regex.search(text)
print m
print text[m.start():m.end()]
#regex.match返回None,因为它只匹配出现在字符串开头的模式,也就是说,无法指定要开始和结束的匹配位置
print regex.match(text)
#还有一个sub方法,会将匹配到的模式替换为指定字符串,并返回新字符串
print regex.sub('REDACTED',text)
#另外,如果想将找出的模式分段,用圆括号括起来即可
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
regex = re.compile(pattern,flags = re.IGNORECASE)
m = regex.match('wesm@bright.net')  #返回一个match对象
print m
print m.groups()  #弄成元组的形式输出
print regex.findall(text) #返回一个列表,每一项都是一个元组
print regex.sub(r'Username: \1, Domain: \2, Suffix: \3',text)  #sub可以利用\1 \2 \3访问被替换的字符串
#看下面的小例子
regex = re.compile(r"""
    (?P<username>[A-Z0-9._%+-]+)
    @
    (?P<domain>[A-Z0-9.-]+)
    \.
    (?P<suffix>[A-Z]{2,4})""",flags = re.IGNORECASE|re.VERBOSE)

#这样可以得到一个简单的字典
m = regex.match('wesm@bright.net')
print m.groupdict()
>>>
['foo', 'bar', 'baz', 'qux']
['foo', 'bar', 'baz', 'qux']
['  ', '\t ', '  \t']
foo \t
['dave@google.com', 'steve@gmail.com', 'rob@gmail.com', 'ryan@yahoo.com']
<_sre.SRE_Match object at 0x03343758>
dave@google.com
None
Dave REDACTED
Steve REDACTED
Rob REDACTED
Ryan REDACTED

<_sre.SRE_Match object at 0x03342A70>
('wesm', 'bright', 'net')
[('dave', 'google', 'com'), ('steve', 'gmail', 'com'), ('rob', 'gmail', 'com'), ('ryan', 'yahoo', 'com')]
Dave Username: dave, Domain: google, Suffix: com
Steve Username: steve, Domain: gmail, Suffix: com
Rob Username: rob, Domain: gmail, Suffix: com
Ryan Username: ryan, Domain: yahoo, Suffix: com

{'username': 'wesm', 'domain': 'bright', 'suffix': 'net'}
[Finished in 0.8s]

复制代码

正则表达式的方法有:

image

  • pandas中矢量化字符串函数
复制代码
#-*- encoding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas import Series,DataFrame
import re

data = {'Dave':'dave@google.com','Steve':'steve@gmail.com','Rob':'rob@gmail.com','Web':np.nan}
data = Series(data)
print data
print data.isnull()
#通过map,所有字符串和正则都能传入各个值(通过lambda或者其他函数),但是如果存在NA就会报错。
#然而,Series有些跳过NA的方法。通过Series的str属性可以访问这些方法。
print '\n',data.str.contains('gmail'),'\n' #查看是否每个包含gmail
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
print data.str.findall(pattern,flags = re.IGNORECASE),'\n'
#print data.str.replace('@','')  #这里的replace可以矢量化应用到每个元素
#有两个办法可以实现矢量化的元素获取操作:要么使用str.get,要么在str属性上用索引
matches = data.str.match(pattern,flags = re.IGNORECASE)
print matches,'\n'
print matches.str.get(1),'\n'
print matches.str[0],'\n'
#可以这样进行截取
print data.str[:5],'\n'
#下面这样只是选取前两个
print data[:2]
>>>
Dave     dave@google.com
Rob        rob@gmail.com
Steve    steve@gmail.com
Web                  NaN
Dave     False
Rob      False
Steve    False
Web       True

Dave     False
Rob       True
Steve     True
Web        NaN

Dave     [('dave', 'google', 'com')]
Rob        [('rob', 'gmail', 'com')]
Steve    [('steve', 'gmail', 'com')]
Web                              NaN

Dave     ('dave', 'google', 'com')
Rob        ('rob', 'gmail', 'com')
Steve    ('steve', 'gmail', 'com')
Web                            NaN

Dave     google
Rob       gmail
Steve     gmail
Web         NaN

Dave      dave
Rob        rob
Steve    steve
Web        NaN

Dave     dave@
Rob      rob@g
Steve    steve
Web        NaN

Dave    dave@google.com
Rob       rob@gmail.com
[Finished in 0.7s]

复制代码

下面矢量化的字符串方法,比较重要。

image

 
 
posted @ 2016-12-28 14:46  贺大卫  阅读(4850)  评论(0编辑  收藏  举报