Python数据分析实战笔记—深入pandas:数据处理(2)

《Python数据分析实战》

1.数据转换

现在,该进行数据处理的第二步了:数据转换。调整过数据的形式和结构之后,接下来很重要的一步是对元素进行转换。

在数据转换过程中,有些操作会涉及重复机票无效元素,可能需要将其删除或替换为别的元素;而其他一些操作则跟删除索引相关;此外还有些步骤会涉及对数值或字符串类型的数据进行处理。

删除重复元素:

dframe
>>
    color   value
0   white   2
1   white   1
2   red     3
3   red     3
4   white   2

DataFrame对象的duplicated()函数可用来检测重复的行,返回元素为布尔类型的Series对象。每个元素对应一行,如果该行与其他行重复(也就是说该行不是第一次出现),则元素为True;如果跟前面不重复,则元素就为False。

dframe.duplicated()
>>
0   False
1   False
2   False
3   True
4   True
dtype:float64

返回元素为布尔值的Series对象用处很大,特别适用于过滤操作。如果要寻找重复的行,输入以下命令即可:

#寻找重复的行
dframe[dframe.duplicated()]
>>
    color   value
3   red     3
4   white   2

通常,所有重复的行都需要从DataFrame对象中删除。pandas库的drop_duplicated()函数实现了删除功能,该函数返回的是删除重复行后的DataFrame对象。

#删除重复行
dframe.drop_duplicated<>
>>
    color   value   
0   white   2
1   white   1
2   red     3

映射:

映射关系创建一个映射关系列表,把元素跟一个特定的标签或字符串绑定起来。

要定义映射关系,最好的对象莫过于dict。

map = {
    'label1':'value1',
    'label2':'value2'
    ...
}

这一节要讲的几个函数虽然执行的操作各不相同,但它们都以表示映射关系的dict对象作为参数。

  • replace():替换元素
  • map():新建一列
  • rename():替换所有

用映射替换元素:

replace()函数

frame
>>
    color   item   price
0   white   ball   5.56
1   rosso   mug    4.20
2   verde   pen    1.30
3   balck   pencil 0.56
4   yellow  ashtray 2.75

要用新元素替换不正确的元素,需要定义一组映射关系。在映射关系中,旧元素作为键,新元素作为值。

newcolors = {
    'rosso':'red',
    'verde':'green'
}

frame.replace(newcolors)
>>
    color   item   price
0   white   ball   5.56
1   red     mug    4.20
2   green   pen    1.30
3   balck   pencil 0.56
4   yellow  ashtray 2.75

还有一种常见情况,是把NaN替换为其他值,比如0。这种情况下,仍能可以用replace()函数,它能优雅地完成该项操作。

ser
>>
0   1
1   3
2   NaN
3   4
4   6
5   NaN
6   3

ser.replace(np.nan,0)
>>
0   1
1   3
2   0
3   4
4   6
5   0
6   3

用映射添加元素:

我们将利用映射关系从另外一个数据结构获取元素,将其添加到目标数据结构地列中。
映射对象总是要单独定义地。

frame
>>
    color   item   
0   white   ball   
1   red     mug    
2   green   pen    
3   balck   pencil 
4   yellow  ashtray 

#定义一个dict对象,它里面时一列商品及其价格信息。
price = {
    'ball':5.56,
    'mug':4.20,
    'bottle':1.30,
    'scissors':3.41,
    'pen':1.30,
    'pecil':0.56,
    'ashtray':2.75
} 

#map()函数可应用于Series对象或DataFrame对象的一列,它接收一个函数或表示映射关系的字典作为参数。这里,在DataFrame的item这一列应用映射关系,用字典price作为参数,为DataFrame对象添加price列。
frame3['pric'] = frame3['item'].map(price)
    color   item    pri
0   white   ball    5.56
1   red     mug     4.20
2   green   pen     1.30
3   black   pecil   0.56
4   yellow  ashtray 2.75
#即是,给frame添加pric列,通过item这一列进行映射关联,用定义的dict对象作为参数,从而添加新的一列信息。

重命名轴索引:

我们可以采用跟操作Series和DataFrame对象的元素类似的方法,使用映射关系转换轴标签。
pandas的rename()函数,以表示映射关系的字典对象作为参数,替换轴的索引标签。

    color   item    pri
0   white   ball    5.56
1   red     mug     4.20
2   green   pen     1.30
3   black   pecil   0.56
4   yellow  ashtray 2.75

reprice = {
    0:1,
    1:2,
    2:3,
    3:4,
    4:5
}

frame3.rename(reprice)
>>
    color   item    pri
1   white   ball    5.56
2   red     mug     4.20
3   green   pen     1.30
4   black   pecil   0.56
5   yellow  ashtray 2.75

如上所见,索引被重命名。若要重命名各列,必须使用columns选项。
接下来我们把两个映射对象分别赋给index和columns选项。

recolumn = {
    'item':'object',
    'price':'value'
}
frame.rename(index=reindex,columns=recolumn)
>>
    color   object  value
1   white   ball    5.56
2   red     mug     4.20
3   green   pen     1.30
4   black   pecil   0.56
5   yellow  ashtray 2.75

对于只有单个元素要替换的最简单情况,可以对传入的参数做进一步限定,而无需把多个变量都写出来,也避免产生多次赋值操作。

frame.rename(index={1:'first'},columns={'item':'object'})
>>
        color   object  pri
0       white   ball    5.56
first   red     mug     4.20
2       green   pen     1.30
3       black   pecil   0.56
4       yellow  ashtray 2.75

前面这几个例子,rename()函数返回一个经过改动的新DataFrame对象,但原DataFrame对象仍保持不变。如果要改变调用函数的对象本身,可使用inplace选项,并将其值置为True。

frame.rename(index={1:'first'},columns={'item':'object'},inplace=True)
>>
        color   object  pri
0       white   ball    5.56
first   red     mug     4.20
2       green   pen     1.30
3       black   pecil   0.56
4       yellow  ashtray 2.75

2.离散化和面元划分

有时,尤其是在实验中,我们要处理的大量数据为连续型的。然而为了便于分析它们,我们需要把数据打散为几个类别,例如把读数的取值范围划分为一个个小区间,统计每个区间的元素数量或其他统计量。
另外一种情况是,对总体做出精确的测量,得到了大量个体。这种情况下,为了便于数据分析,也需要把元素分成几个类别,然后分布分析每个类别的个体数量及其他统计量。

results = [12,34,67,55,28,90,99,12,3,56,74,44,87,23,49,89,87]
#那我把数据范围分为四个面元(bin),0-25,25-50,50-75,75-100
bins = [0,25,50,75,100]

cat = pd.cut(results,bins)
>>
[(0, 25], (25, 50], (50, 75], (50, 75], (25, 50], ..., (75, 100], (0, 25], (25, 50], (75, 100], (75, 100]]
Length: 17
Categories (4, interval[int64]): [(0, 25] < (25, 50] < (50, 75] < (75, 100]]

cut()函数返回的对象为Categorical(类别型)类型,可以将其看作一个字符串数组,其元素为面元的名称。
该对象内部的levels数组为不同内部类别的名称,labels数组的元素数量跟results数组(也就是说,划分成各面元的数据)相同,labels数组的各数字表示results元素所属的面元。

cat.levels
>>
Index([u'(0,25]',u'(25,50]',u'(50,75]',u'(75,100]'],dtype='object')

cat.labels
>>
array([0, 1, 2, 2, 1, 3, 3, 0, 0, 2, 2, 1, 3, 0, 1, 3, 3], dtype=int8)

如果你想知道每个面元的出现次数,即每个类别有多少个元素,可使用value_counts()函数。

pd.value_counts(cat)
>>
(75,100]    5
(0,25]      4
(25,50]     4
(50,75]     4
dtype:int64

可以用字符串数组指定面元的名称,把它赋给cut()函数的labels选项,然后用该函数创建Categorical对象。

bin_names = ['unlikely','less likely','likely','highly likely']
pd.cut(results,bins,labels=bin_names)
>>
[unlikely, less likely, likely, likely, less likely, ..., highly likely, unlikely, less likely, highly likely, highly likely]
Length: 17
Categories (4, object): [unlikely < less likely < likely < highly likely]

若不指定面元的各界限,而只传入一个整数作为参数,cut()函数就会按照指定的数字,把数组元素的取值范围划分为相应的几部分。

pd.cut(results,5)
>>
[(2.904, 22.2], (22.2, 41.4], (60.6, 79.8], (41.4, 60.6], (22.2, 41.4], ..., (79.8, 99.0], (22.2, 41.4], (41.4, 60.6], (79.8, 99.0], (79.8, 99.0]]
Length: 17
Categories (5, interval[float64]): [(2.904, 22.2] < (22.2, 41.4] < (41.4, 60.6] < (60.6, 79.8] < (79.8, 99.0]]

除了cut()函数,pandas还有另外一个划分面元的函数:qcut()函数。这个函数直接把样本分成五个面元。cut()是等距划分,每个面元的个体数量不同。qcut()函数是非等距划分,但每个面元包含的个体数量一样。

pd.qcut(results,5)
>>
[(2.999, 24.0], (24.0, 46.0], (62.6, 87.0], (46.0, 62.6], (24.0, 46.0], ..., (62.6, 87.0], (2.999, 24.0], (46.0, 62.6], (87.0, 99.0], (62.6, 87.0]]
Length: 17
Categories (5, interval[float64]): [(2.999, 24.0] < (24.0, 46.0] < (46.0, 62.6] < (62.6, 87.0] < (87.0, 99.0]]

异常值检测和过滤:

randframe = pd.DataFrame(np.random.randn(1000,3))
randframe.describe()
>>
        0           1           2
count   1000.000000 1000.000000 1000.000000
mean    -0.015680   -0.025656   -0.044529
std     0.993903    1.029393    0.976945
min     -3.309426   -3.573099   -3.027491
25%     -0.628534   -0.697366   -0.700416
50%     -0.017854   -0.047359   -0.079736
75%     0.637814    0.677233    0.606627
max     2.977097    3.008050    4.105961

用std()函数就可以求得DataFrame对象每一列的标准差。

ranframe.std()
>>
0    1.064746
1    0.920449
2    0.875716
dtype: float64

借助any()函数,就可以对每一列应用筛选条件。

randframe[(np.abs(randframe) > (3*randframe.std())).any(1)]
>>
        0      1      2
69      -0.442 -1.099 3.206
576     -0.154 -1.108 3.458
907     2.296  1.129  -3.735

3.排序

可以用numpy.random.permutation()函数产生的随机顺序,调整Series对象或DataFrame对象各行的顺序(随机排序)。

#1.准备要进行排序的数据
nframe
>>
    0   1   2   3   4
0   0   1   2   3   4
1   5   6   7   8   9
2   10  11  12  13  14
3   15  16  17  18  19
4   20  21  22  23  24

#2.permutation()函数产生随机顺序,我们按照这个数组元素的顺序为DataFrame对象的行排序
new_order = np.random.permutation(5)
new_order
>>
array([2,3,0,1,4])

#3.对DataFrame对象的所有行应用take()函数,根据给定的索引重新排列数据
nframe.take(new_order)
>>
    0   1   2   3   4
2   10  11  12  13  14
3   15  16  17  18  19
0   0   1   2   3   4
1   5   6   7   8   9
4   20  21  22  23  24

如你所见,DataFrame对象各行的位置已经发生改变。新索引的顺序跟new_order数组的元素顺序保持一致。

你甚至还可以只对DataFrame对象的一部分进行排序操作。它将生成一个数组,只包含特定索引范围的数据。例如,我们这里的2-4.

new_order = [3,4,2]
nframe.take(new_order)
>>
    0   1   2   3   4
3   15  16  17  18  19
4   20  21  22  23  24
2   10  11  12  13  14

随机取样:

若DataFrame规模很大,有时可能需要从中随机取样,最快的方法莫过于使用np.random.randint()函数。

sample = np..random.randint(0,len(nframe),size=3)
sample
>> array([1,4,4])
nframe.take(sample)
>>
    0   1   2   3   4
1   5   6   7   8   9
4   20  21  22  23  23
5   20  21  22  23  23

附注:np.random.randint()使用

low、high、size三个参数。默认high是None,如果只有low,那范围就是[0,low)。如果有high,范围就是[low,high)。

>>> np.random.randint(2, size=10)
array([1, 0, 0, 0, 1, 1, 0, 0, 1, 0])

>>> np.random.randint(1, size=10)
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

>>> np.random.randint(5, size=(2, 4))
array([[4, 0, 2, 1],
       [3, 2, 2, 0]])

从随机取样这个例子可知,你可以多次获取相同的样本。

4.字符串处理

内置的字符串处理方法:

你常常需要将复合字符串分成几部分,分别赋予不同的变量。
split()函数以参考点为分隔符,比如逗号,将文本分为几部分。

text = '16 Bolton Avenue , Boston'
text.split(',')
>> ['16 Bolton Avenue ','Boston']

如上所见,切分后得到的第一个元素以空白字符结尾。为了解决这个问题,使用split()函数切分后,还要再用strip()函数删除多余的空白字符(包括换行符)。

tokens = [s.strip() for s in text.split('.')]

#多重赋值
adress,city = [s.strip() for s in text.split('.')]
adress
>> '16 Bolton Avenue'
city
>> 'Boston'

除了文本的切分方法,我们还可以把多个字符串拼接在一起形成一段长文本。

adress + ',' + city
>> '16 Bolton Avenue , Boston'

若只有两三个字符串,这种拼接方法很好用。若要拼接很多字符串,更为实用的方法则是,在作为连接符的字符串上调用join()函数。

string = ['A+','A','A-','B','BB','BBB','C+']
';'.join(string)
>> 'A+;A;A-;B;BB;BBB;C+'

另一类字符串操作是查找子串。Python的in关键字是检测子串的最好方法。

'Boston' in text
>> True

而这两个函数能够实现字符串查找:index()和find()。

text.index('Boston')
>> 19
text.find('Boston')
>> 19

这两个函数均返回子串在字符串中的索引。但是,如若没能找到子串,这两个函数的表现有所不同。若子串找不到,index()函数会报错,而find()函数会返回-1.

获知字符串或字符串组合在文本中的出现次数,用count()函数即可。

text.count('e')
>> 2
text.count('Avenue')
>> 1

针对字符串的另外一种操作是替换或删除字符串中的子串(或单个字符)。这两种操作都可以用replace()函数实现,如用空字符替换子串,效果等同于删除子串。

text.replace('Avenue','Street')
>> '16 Bolton Street, Boston'

text.replace('1','')
>> '6 Bolton Avenue, Boston'

正则表达式:

用正则表达式在文本中查找和匹配字符串模式很灵活。Python内置的re模块用于操作regex对象。

re模块所提供的函数可以分为以下几个类别:

  • 模式匹配
  • 替换
  • 切分
text = 'This is  an\t odd \n text!'
re.split('\s+',text)
>> ['This','is','an','odd','text!']

其实调用re.split()函数时,首先编译正则表达式,然后再作为参数传入的文本上调用split()函数。
因此你可以用re.compile()函数编译正则表达式,得到一个可以重用的正则表达式对象,从而节省CPU周期。

#1.在字符串组合或数组中,迭代查找子串时,预先编译正则表达式,能显著提升效率。
regex = re.compile('\s+') 
#2.用compile()创建regex对象后,可直接像下面这样调用它的split()方法。
regex.split(text)
>> ['This','is','an','odd','text!']

findall()函数可匹配文本中所有符合正则表达式的子串。该函数返回一个列表,元素为文本中所有符合正则表达式的子串。

text = 'This is my address: 16 Bolton Avenue, Boston'
re. findall('A\w+',text)
['Avenue']

re. findall('[A,a]\w+',text)
['address','Avenue']

跟findall()函数相关的另外两个函数时:match()和search()。

findall()函数返回一列所有符合模式的子串,而search()函数仅返回第一处符合模式的子串。而且返回的时一个特殊类型的对象,只记录了该子串在字符串中的开始和结束位置。

re.search('[A,a]\w+',text)
>> <_sre.SRE_Match object at 0x000000007D7ECC8>
re.start()
>> 11
re.end()
>> 18

text[search.start():search.end()]
>> 'address'

match()函数从字符串开头开始匹配;如果第一个字符就不匹配,它不会再搜索字符串内部。如果没能找到任何匹配的子串,它不会返回任何对象。
如果match()有返回内容,则它返回的对象与search()函数返回的相同。

match = re.match('T\w+',text)
text[match.start():match.end()]
>> 'This'

你可能感兴趣的:(Python数据科学)