通过使用编程语言,我们可以1)使用更加强大的的结构和方法,例如使用数组去存储和检索变量,2)编写可重复运行的脚本。
然而,你可能会认为使用Excel会更容易的理解数据。(当你想看某一列时,你只需要鼠标滚轮滑到那列即可)另一方面,你的统计学朋友告诉你使用R会更好,R有“data.frame”的概念。好吧,在本教程中,我们使用另一种方式来为数据和模型之间搭一座桥梁。
Python有一个很棒的包叫做Pandas,它可以让数据探索和数据清洗变得比操作数组更容易。而且它让你的代码更加易读。Pandas也有数据框架(dataFrame)的概念。最后,如果你去论坛中搜索其他教程的话,你会发现那些作者也用Pandas。
本教程与之前的两个有一些区别:没有粘性的可运行脚本,在数据页面没有样例.py文件。取而代之的是,本教程列出python命令行,这样,你可以自己学习一些方法,然后观察发生了什么。你甚至可以使用你自己感兴趣的方法。最后,命令的输出有时候会非常长,这里不会将之全部显示出来。
Ready?如果你已经安装了ipython或ipython notebook,启动它吧。
Numpy数组
先让我们复习一下我们的train.csv数据在python中是长什么样的。再次运行一下导入数据的脚本:
import csv as csv
import numpy as np
csv_file_object = csv.reader(open('../csv/train.csv', 'rb'))
header = csv_file_object.next()
data=[]
for row in csv_file_object:
data.append(row)
data = np.array(data)
现在输入print data
[['1' '0' '3' ..., '7.25' '' 'S']
['2' '1' '1' ..., '71.2833' 'C85' 'C']
['3' '1' '3' ..., '7.925' '' 'S']
...,
['889' '0' '3' ..., '23.45' '' 'S']
['890' '1' '1' ..., '30' 'C148' 'C']
['891' '0' '3' ..., '7.75' '' 'Q']]
这些都是相似的。。。一个csv包能够读取的字符串数组。
观察Age列的前15行:data[0:15,5]
array(['22', '38', '26', '35', '35', '', '54', '2', '27', '14', '4', '58', '20', '39', '14'],
dtype='|S82')
很好,这条命令仅给出了年龄,而且它们仍为字符串。整个列的对象类型是什么呢?
type(data[0::,5])
numpy.ndarray
我们从数据中获取的任意的切片仍然是一个Numpy数组。现在我们看看我们能否计算游客年龄的均值。它们应该是浮点类型:
ages_onboard = data[0::,5].astype(np.float)
ValueError: could not convert string to float:
额。看起来只对前几行管用,当处理第6行的缺失数据‘’时numpy就产生错误了。由此我们就必须使用python去过滤缺失值、转换到浮点型,再计算均值--但这不再简单了。那么我们使用Pandas再试一遍看看。
Pandas数据框架
我们需要做的第一件事情是import Pandas包。Pandas拥有自己的读写.csv文件的函数,因此我们不在需要csv包。我们创建一个新的对象‘df’来存储train.csv版本的pandas。
import pandas as pd
import numpy as np
# For .read_csv, always use header=0 when you know row 0 is the header row
df = pd.read_csv('train.csv', header=0)
看看出现了什么:
df
(...long list of stuff...!)
...
...
...
891 rows × 12 columns
这看起来没什么用。让我们仅看前几行:
df.head(3)
(a short list of stuff!)
...
3 rows × 12 columns
你注意到它有列名,边上还有行的序号。(注意,你也可以试试df.tail(3))现在,比较原始的数据数组,这个数据对象的类型是什么?
type(df)
pandas.core.frame.DataFrame
回顾之前使用csv包的结果,每个值都被读取为字符串类型。现在使用pandas自己的csv读取器的结果如何?
df.dtypes
PassengerId int64
Survived int64
Pclass int64
Name object
Sex object
Age float64
SibSp int64
Parch int64
Ticket object
Fare float64
Cabin object
Embarked object
dtype: object
Pandas能够探测到数值类型。因此我们已经有一些存为整数的值了。当它检测到双精度点的时候,它会自动将其转化为float类型。这里有两个更加实用的命令:
df.info()
Int64Index: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId 891 non-null int64
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object
Sex 891 non-null object
Age 714 non-null float64
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object
Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
这里有好多有用的信息!你可以直观的知道这里有891项(行),大部分变量都是完整的(891为non-null),但除了Age、Cabin、Embarked——这些列在某处都有空值。现在试试:
df.describe()
PassengerId Survived Pclass Age ...
count 891.000000 891.000000 891.000000 714.000000 ...
mean 446.000000 0.383838 2.308642 29.699118 ...
std 257.353842 0.486592 0.836071 14.526497 ...
min 1.000000 0.000000 1.000000 0.420000 ...
25% 223.500000 0.000000 2.000000 20.125000 ...
50% 446.000000 0.000000 3.000000 28.000000 ...
75% 668.500000 1.000000 3.000000 38.000000 ...
max 891.000000 1.000000 3.000000 80.000000 ...
这个同样有用:pandas列出了所有数值列,而且快速地算出了均值、标准差、最小和最大值。非常方便!但要注意的是:我们知道在Age中有很多缺失值,pandas是怎么处理它们的呢?它必须遗留下很多空值。那么如果我们查询“Titanic上年龄均值”,我们需要告诫我们是如何得到这个数字的。
数据整理
任何数据分析中都有一个数据清洗的步骤。Pandas让数据过滤、制造、剔除、填充和替换都变得非常简单。下面我们学习pandas对特定列操作的语法。
引用与过滤
让我们看一下Age列的前10行。在pandas中是这样的
df['Age'][0:10]
0 22
1 38
2 26
3 35
4 35
5 NaN
6 54
7 2
8 27
9 14
Name: Age, dtype: float64
-->试试可替换的语法:df.Age[0:10]
-->不要数下标,你现在能显示Cabin列了吗?
与之前一样,让我们看看这个对象的类型:
type(df['Age'])
pandas.core.series.Series
一个单列既不是numpy的数组类型,也非pandas数据框架——而是一个特定的pandas对象,叫做数据序列(series)
我们如果想得到均值:
df['Age'].mean()
29.69911764705882
这和df.describe()中描述的是一样的。
-->看看你能不能获得Age的中值。
我们需要做的下一件事情就是观察dataframe中特定的子集。Pandas使之写起来非常方便。a[list]的形式即可:
df[['Sex','Pclass','Age']]
Sex Pclass Age
0 male 3 22.0
1 female 1 38.0
2 female 3 26.0
3 female 1 35.0
4 male 3 35.0
5 male 3 NaN
... ... ...
... ... ...
[891 rows x 3 columns]
如果我们手动研究数据的话,过滤数据是另一个十分重要的工具。.describe()命令已经告诉我们最大年龄是80。老人在数据集中是什么样的?
df[df['Age']>60]
(a medium list of stuff!)
...
...
22 rows × 12 columns
如果你对这些游客的性别和Pclass和他们是否幸存感兴趣的话:
Sex Pclass Age Survived
33 male 2 66.0 0
54 male 1 65.0 0
96 male 1 71.0 0
116 male 3 70.5 0
170 male 1 61.0 0
252 male 1 62.0 0
275 female 1 63.0 1
280 male 3 65.0 0
... ... ... ...
... ... ... ...
[22 rows x 4 columns]
现在是时候研究所有的这些缺失的Age值,如果我们希望使用更加先进的算法,我们就要解决这些值。过滤缺失值,使用:
df[df['Age'].isnull()][['Sex', 'Pclass', 'Age']]
(a long list of stuff!)
...
...
...
177 rows × 3 columns
这里我们仅仅显示了177行,但用同样的语法我们可以对其进行操作。
连接多个标准也是非常有用的(使用&)。让我们计算一个每个类别中男性的数量:
for i in range(1,4):
print i, len(df[ (df['Sex'] == 'male') & (df['Pclass'] == i) ])
1 122
2 108
3 347
在我们完成初始的手动研究之前,先看看另一个非常方便的pandas函数,它可以为任一列绘制直方图。pandas直方图函数比起matplotlib/pylab而言比较方便,输入以下代码:
import pylab as P
df['Age'].hist()
P.show()
df['Age'].dropna().hist(bins=16, range=(0,80), alpha = .5)
P.show()
在Pandas中,增加一列非常简单:
df['Gender'] = 4
显示一些.head()行看看我们刚刚做了什么。现在让我们把它的之变为Sex的首字母大写形式:
df['Gender'] = df['Sex'].map( lambda x: x[0].upper() )
lambda x 是python中内建的匿名函数。记住,x[0]返回任何字符串的首个字符。
看看现在.head()行变成什么样了?
当然我们需要的是一个二元整数来表示男性和女性,就和Survived列一样。为了与其一致,我们让Gender列映射到0,1。我们有先分析女性的先例,因此我们决定female=0,male=1.
df['Gender'] = df['Sex'].map( {'female': 0, 'male': 1} ).astype(int)
看看现在.head()行又变成什么样了?
-->你能对Embarked值做相似的事吗?
因为大部分机器学习需要一个完整的特征列,现在我们需要处理Age中缺失的数据。如果我们随便猜测数据填进去,这就相当于想模型中加入了一些噪音,但如果我们猜测是有根据的,一些值是接近历史事实的,那么模型的效果必然是比随机猜测要好。我们知道已知数据的均值是29.6991176——我们应该把空值替换为这个值。但是不是中值会更好呢?(会减少70-,80-的影响)Age直方图确实看起来是倾斜的。这些是你在Kaggle比赛中应当做的决定之一。
现在,让我们的决定更加复杂,我们希望使用游客类别来划分填入的年龄值。同时决定使用中值会更好。让我们建立另一个参照表存储这些值:
median_ages = np.zeros((2,3))
median_ages
生成:
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
计算数组:
for i in range(0, 2):
for j in range(0, 3):
median_ages[i,j] = df[(df['Gender'] == i) & \
(df['Pclass'] == j+1)]['Age'].dropna().median()
median_ages
生成:
array([[ 35. , 28. , 21.5],
[ 40. , 30. , 25. ]])
我们可以直接向Age列填入这些值。但为了区分哪些地方是缺失的,我们还是新建一列AgeFill比较好。
做一个Age的拷贝:
df['AgeFill'] = df['Age']
df.head()
看看,Age列值为空,
我们关注的几列
现在变成什么样了:
df[ df['Age'].isnull() ][['Gender','Pclass','Age','AgeFill']].head(10)
Gender Pclass Age AgeFill
5 1 3 NaN NaN
17 1 2 NaN NaN
19 0 3 NaN NaN
26 1 3 NaN NaN
28 0 3 NaN NaN
29 1 3 NaN NaN
31 0 1 NaN NaN
32 0 3 NaN NaN
36 1 3 NaN NaN
42 1 3 NaN NaN
用median_ages中的值填到AgeFill列中。
for i in range(0, 2):
for j in range(0, 3):
df.loc[ (df.Age.isnull()) & (df.Gender == i) & (df.Pclass == j+1),\
'AgeFill'] = median_ages[i,j]
看看我们之前观察的前10行变成什么样了:
df[ df['Age'].isnull() ][['Gender','Pclass','Age','AgeFill']].head(10)
Gender Pclass Age AgeFill
5 1 3 NaN 25.0
17 1 2 NaN 30.0
19 0 3 NaN 21.5
26 1 3 NaN 25.0
28 0 3 NaN 21.5
29 1 3 NaN 25.0
31 0 1 NaN 35.0
32 0 3 NaN 21.5
36 1 3 NaN 25.0
42 1 3 NaN 25.0
这证实了我们想要做的已经完成了。
让我们在创建一个记录原始Age为缺失的特征:
df['AgeIsNull'] = pd.isnull(df.Age).astype(int)
现在,我们有3个新的数值列:Gender、AgeFill、AgeIsNull。你可以用df.describe()看看整个dataframe的统计信息。
特征工程
我们可以通过一些简单数学运算创建一些其他的特征。我们知道Parch是游客船上父母或子女的数量,SibSp是游客兄弟姐妹或配偶的数量,我们可以将之结合为新的特征FamilySize:
df['FamilySize'] = df['SibSp'] + df['Parch']
如果我们认为对机器学习有益,我们甚至可以人造出一些特征。例如,我们知道Pclass和Age对是否幸存有很大影响。一种人造特征的方式是将两者相乘,这个特征放大了第三类游客和老年乘客的值。这两个都很难存活,因此这个理论应该是有用的。
df['Age*Class'] = df.AgeFill * df.Pclass
我们可以绘制一些直方图来更好地理解这些新的特征。
最后准备
我们现在已经有几乎适用于机器学习的数据了。但大部分基础的ML技术都无法在字符串上工作,在python中它们也要求数据为数组类型——在sklearn包中,数据要求为数组,而不是pandas的dataframe。因此最后我们要做两件事:1)决定我们需要留下那些列,2)将pandas.DataFrame转化回numpy.array。
在pandas中你可以用.info()方法查看列的类型。或者直接输入:
df.dtypes
PassengerId int64
Survived int64
Pclass int64
Name object
Sex object
Age float64
SibSp int64
Parch int64
Ticket object
Fare float64
Cabin object
Embarked object
Gender int64
AgeFill float64
AgeIsNull int64
FamilySize int64
Age*Class float64
dtype: object
让dtype仅显示object类型的列:
df.dtypes[df.dtypes.map(lambda x: x=='object')]
Name object
Sex object
Ticket object
Cabin object
Embarked object
dtype: object
(你可能已经将‘Embarked’类转换过了)
下一步是去掉我们不用的这些列:
df = df.drop(['Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1)
因为我们已经创建了新的无空值的AgeFill列,我们也可以将'Age'删除。
df = df.drop(['Age'], axis=1)
去除有
缺失值的
任意行的命令为:
df = df.dropna()
但要注意,.drop()会移除哪怕只有一列是空值的行。
现在我们有一个干净且紧凑的数据集了。
最终的步骤是将其转化回Numpy数组。使用Pandas.values方法可以轻松做到:
train_data = df.values
train_data
array([[ 1. , 0. , 3. , ..., 0. , 1. , 66. ],
[ 2. , 1. , 1. , ..., 0. , 1. , 38. ],
[ 3. , 1. , 3. , ..., 0. , 0. , 78. ],
...,
[ 889. , 0. , 3. , ..., 1. , 3. , 64.5],
[ 890. , 1. , 1. , ..., 0. , 0. , 26. ],
[ 891. , 0. , 3. , ..., 0. , 0. , 96. ]])
与原始的数组比较:
data
array([['1' '0' '3' ..., '7.25' '' 'S']
['2' '1' '1' ..., '71.2833' 'C85' 'C']
['3' '1' '3' ..., '7.925' '' 'S']
...,
['889' '0' '3' ..., '23.45' '' 'S']
['890' '1' '1' ..., '30' 'C148' 'C']
['891' '0' '3' ..., '7.75' '' 'Q']],
dtype='|S82')
本文原文:点击打开链接
数据下载:train.scv