本文为Datawhale8月组队学习——动手学数据分析课程的系列学习笔记。
Datawhale-动手学数据分析
数据来源
Kaggle小白入门首选练手项目——Kaggle-泰坦尼克号存活率
Ch1 数据加载与探索性分析
数据分析包含数据加载、探索性数据分析、数据清洗、特征处理、数据建模、模型评估等多个步骤。在进行数据分析之前,需要载入我们获取的数据集,并通过探索性分析初步了解数据的结构、组成和特征。
Ch1-1 数据载入与初步观察
数据载入
载入函数
对于常见的数据文件类型(.csv/.xlsx/.tsv/...),通常使用第三方库pandas载入数据。
pandas中,常见的载入数据文件函数有以下几种:
- read_table():可以读取常见的分隔符定界文件,
sep=''
参数用于选择分隔符,若该参数为空,则不予分隔(所有数据集中在一列中),支持正则表达式。 - read_csv():读取逗号分隔符文件(.csv),同样有
sep=''
参数,默认为逗号(,) - read_excel():读取Excel文件(.xlsx),相比前两种函数,对于含有多个工作簿的excel文件,一般需要输入参数
sheetname=''
,不输入则默认为第一张工作簿
以上函数默认返回DataFrame对象,对于excel,当读入多个工作簿(如:[1,2])时,返回一个dict对象,每个元素的值都是一个DataFrame对象。
除此之外,pandas还支持读取很多其他的文件格式(如:JSON、pickle、SQL以及常见统计软件STATA/SPSS的输出格式),具体参考pandas官方文档-IO
相对路径与绝对路径
查看当前所在路径的命令:os.getcwd()
使用前要导入python自带的输入输出库os
相对路径表述:
符号 | 含义 |
---|---|
/ | 根目录 |
./ | 当前目录 |
../ | 上一级目录 |
../../ | 上两级目录,多级目录以此类推 |
逐块读取
当数据文件过大,包含数据量过多时,为了防止一次性读入所有数据服务器内存占用过大,难以处理,pandas的载入函数提供了chunksize
参数以实现数据的逐块读取,该参数使得函数通过分多次将文件数据读入内存,降低了内存占用。
# 逐块读取
chunks = pd.read_csv('../../Titanic-kaggle/data/train.csv', chunksize = 1000)
print(type(chunks))
for piece in chunks:
print(type(piece))
print(len(piece))
当使用chunksize
参数时,载入函数将返回TextFileReader对象,该对象可以使用for语句遍历,其中每个元素都是一个包含指定行数的DataFrame对象,此时就可以在循环中实现对各块数据的批处理。
设定列名和索引
- 可以在读取数据文件时利用参数
names=[]
、header=0
、index='index_col_name'
设定。 - 也可以在后期通过方法
df.set_index()
、df.rename(colomns={})
或借助属性df.columns
、df.index
暴力修改
Tips:
- 当使用read_csv()的names参数修改列名时,其实质是在原表基础上加上给定的列名,此时header会取Null;若不使用names参数,header默认取0。因此,设定列名时,应当使用header=0来表明原数据有列名,且位于第一行,这样才能实现列名的替换。
- df.set_index()中包含drop参数,该参数应设定为True,表示删除现有索引列,否则当前索引行将变为普通列加入现有DataFrame
# 方法1:读取时设定
names = ['乘客ID','是否幸存','乘客等级(1/2/3等舱位)','乘客姓名','性别','年龄','堂兄弟/妹个数','父母与小孩个数','船票信息','票价','客舱','登船港口']
# header属性用于设置列名取自哪一行,指定names时应当设为0,否则会多出原标题行
df = pd.read_csv('../../Titanic-kaggle/data/train.csv',names= names,index_col='乘客ID',header=0)
# 方法2:更改表头和索引
column_names = {'PassengerId':'乘客ID','Survived':'是否幸存','Pclass':'乘客等级(1/2/3等舱位)','Name':'乘客姓名','Sex':'性别','Age':'年龄','SibSp':'堂兄弟/妹个数','Parch':'父母与小孩个数','Ticket':'船票信息','Fare':'票价','Cabin':'客舱','Embarked':'登船港口'}
df.rename(columns = column_names, inplace = True)
df.set_index('乘客ID', drop = True, inplace = True)
初步观察
概览数据基本信息
查看数据的基本信息主要用到以下一些方法和属性:
方法/属性 | 用途 |
---|---|
df.shape | 以元组形式返回dataframe对象的行列数 |
df.size | 以整数形式返回dataframe对象的元素数(不包含索引/表头) |
df.colomns | 输出所有的列名 |
df.head(n)/df.tail(n) | 查看dataframe前n行/后n行 |
df.info() | 输出关于数据的基本描述,包含行数、各列的列名、数据类型、非空值数以及占用内存。其中verbose参数可以用于选择长/短两种描述 |
df.describe() | 输出各列的描述性统计信息,可以迅速查看数据的统计特征(Series也有该方法) |
df.value_counts() | 返回一个包含不同列各值数的Series(Series也有该方法) |
判断缺失值
用到两个方法:
- df.isnull():返回一个判断是否为空的DataFrame,若为空则为True,反之为False
- df.isnotnull():返回一个判断是否为非空的DataFrame,若为非空则为True,反之为False
数据输出
与读取的几个函数类似,语法基本一致,区别在于数据输出使用的是对象的方法,而非函数:
- df.to_csv()
- df.to_excel()
Notes:
- 查阅API文档,发现没有to_table()方法,很神奇的是pandas自带了to_latex()和to_markdown()方法
- 一般会设置编码方式参数
encoding='utf-8'
,当元素含有中文时,若出现乱码,可以尝试使用utf_8_sig或gbk格式
Ch1-2 pandas基础
数据类型
pandas最基本的两种数据类型:DataFrame和Series。此外还需要了解numpy中的基本数据类型——ndarray。
ndarray:numpy中最基础的数据类型,多维数组对象,本质上就是一个n维矩阵。只能存放同类型数据。
Series:主要由两部分组成,index 和 values。index可以是任意类型,不一定非是数字类型,values是存放的内容,是一个1维的ndarray
-
DataFrame: 可以看作由多列Series组成,也可以看作由多行Series组成。或者可以看作columns, index, values这三部分组成。
- columns:列名,默认也是数字升序,可以是任意类型
- index:行名,默认数字升序,可以是任意类型
- values:存放的内容,是一个2维的ndarray
简单来说,Series实质是一维数组,DataFrame则是多个Series组成的二维数组,当然二者相比ndarray要多出一些如索引、列名等的属性,可以看作ndarray的包装。
官方文档里写到二者的关系:
DataFrame can be thought of as a dict-like container for Series objects.(DataFrame可以看作一个类似dict的用于盛放Series的容器)
构造方法
Series
- List + index_list
- Dict
- ndarray + index_list
使用 List 进行创建,自动添加0开始的行标签(索引)
Series 创建时若不注明 name
参数,相当于此列没有列名
## Series的创建
# 01 使用list
sdata = [2000,500,1000,4000]
example_1 = pd.Series(sdata)
# 02 使用dict
sdata = {'thu':1,'zju':3,'hust':8,'whu':9,'sysu':10}
example_1 = pd.Series(sdata)
example_1
# 03 使用np.ndarray
sdata = np.random.rand(10)*20
example_1 = pd.Series(sdata,index=['a','b','c','d','e','f','g','h','i','j'],name='random')
无论是字典,列表还是元组,都可以构建Series。只不过,dict自带index,而list,tuple要专门定义index(也就是每一行的行名)。系统默认的index为0,1,2,3…
DataFrame
DataFrame的创建方法有很多种,这里只列了其中几种,具体可以参考创建DataFrame的7种方法
- DataFrame(Dict):字典内可以为列表/字典/Series
- DataFrame.from_dict(Dict)
- DataFrame(np.ndarray)
可以添加index
和columns
参数,不附带index
参数则索引默认为自然数序列
## DataFrame的创建
# 01 使用dict,其中key为list(亦可字典套字典)
ddata = {'country':['US','Brazil','India','Europe','South Africa'],'confirmed':[5481795,3407354,2702742,930276,592744],'death':[171799,109888,51797,15836,12264]}
example_2 = pd.DataFrame(ddata)
print(example_2)
print('--------------')
# 02 from_dict()静态方法
example_2 = pd.DataFrame.from_dict(ddata)
print(example_2)
print('--------------')
# 03 二维数组
ddata = np.array([4,1,4,5,5,2,3,5,6,3,3,4,6,1,9]).reshape(5,3)
example_2 = pd.DataFrame(ddata, index=list('abcde'), columns=['four', 'one', 'three'])
print(example_2)
有些时候我们只需要选择dict中部分的键当做DataFrame的列,那么我们可以使用columns参数,例如我们只选择country和death列:
pd.DataFrame(data = ddata,columns=['country','death'])
数据操作
列选取
- 作为属性选取:
df.Cabin
- 使用[]运算符,其实现方式是实现类的_getitem_魔术方法:
df['Cabin']
很多时候会截取所有数据的一部分进行后续操作和分析,这个时候很可能需要用到
reset_index(drop=True)
方法来重新生成数字索引
行/列删除
- df.drop():
axis
参数默认为0,即删除行,要改成列应改为1。或者忽略axis
直接使用columns
参数 - del df['']:del操作符,只能用于列
很多操作方法都有
inplace
参数,inplace
为True表示直接在原对象上进行改动,默认为False,返回一个新对象
数据筛选
Pandas有自带的访问器操作,loc
与 iloc
iloc
基于位置(数字索引)选择,通过其在 DataFrame 中的数字排位进行访问。
loc
则通过自定义标签进行提取,该方法聚焦于数据的标签(索引)而不是位置(数字索引)。
## iloc
# 取出第一行的内容
df.iloc[0]
# 取出第一列的内容
df.iloc[:, 0]
# 也可以采用内嵌列表的方式
df.iloc[[0, 1, 2], 0]
## loc
# 选中第一行的Sex列对应的单元格
df.loc[0, 'Sex']
# 选中Pclass, Name, Sex这三列的数据
df.loc[:, ['Pclass', 'Name', 'Sex']]
可以使用负数来进行选择:
# 选择倒数五行的内容
df.iloc[-5:]
Notes:
二者的使用方法都是使用中括号
[]
而不是小括号()
loc
与iloc
均采用了先选行后选列的语法,这与传统的 python 语法相反二者的区别
iloc
的区间满足前闭后开,而loc
则满足前闭后闭。因而当我们遇到 String 类型索引,需要按照索引进行选取内容时,我们往往是希望取出区间内所有的元素,此时更好的方法是使用loc
特定条件筛选
loc
访问器操作和[]
运算符可以根据输入的逻辑值Series来筛选显示的行,将自动从中选取逻辑值为True的行。
一些常见的逻辑相关符号和方法:
- 用于连接多个条件的符号:使用 & 表示逻辑和,使用 | 表示逻辑或
- df.isin([]):用于判断是否包含在XXX内,相当于SQL语言中的 WHERE ... IN...
- df.isnull():用于判断是否为空值
- df.notnull():用于判断是否非空
# []运算符
df[(df['Age']>10) & (df['Age']<50)]
# loc访问器操作
df.loc[df['Cabin'].isin(['C123','C85'])]
Ch1-3 探索性数据分析
数据排序
可以按值排序,也可以按索引排序。
String
类型自动按字母顺序排序。
- sort_values():按值排序,
by=[<列1>,<列2>]
参数用于选择排序依据列,可以按多列进行综合排序 - sort_index():按索引排序,
axis
用于选择按行索引/列索引排序,默认为0(列索引)
二者都有控制升降序的参数
ascending=True
,True表示按升序,False表示按降序
一些探索性发现
通过使用describe()
、info()
、corr()
、value_counts()
等函数对数据进行探索性分析,有以下一些发现:
- 船舱信息中存在大量缺失值,年龄信息也有一部分缺失,需要对这些缺失值做一定的处理。
- 性别数据需处理为0/1变量。
得到的一些信息:
- 大多数乘客的家庭成员都很少。
- 乘客姓名第一个单词相同者拥有相同的船票信息、票价、登船港口、客舱、家庭成员人数,这些人应该属于同一个家族。大家族成员的存活率普遍偏低,因此,可以将家庭人数指标纳入后续的模型中。
- 乘客整体的平均年龄在29.7岁。相比整体数据,幸存人群大约只占所有人的1/3。观察其中几个方差较小的指标,其中,幸存人数的乘客等级整体偏高。表明乘客等级确实与幸存率有着一定的关系。
- 票价整体偏低,按照乘客舱位等级和年龄降序排序,发现前20中只有一人存活,这可能暗含着舱位较低的死亡风险更高的信息,猜测可能是舱位低安全措施越不足,安全风险更高的原因。在相关系数的分析中,票价与乘客等级负相关性较强,符合常识,可以考虑将二者结合为一个新的综合指标,进一步分析该指标和是否存活的关系。
- 尽管船上的男性多于女性,女性的存活率却明显高于男性,女性存活率约为74.2%,相比之下男性只有18.9%的存活率。因此,性别可能也可以作为预测模型的考虑因素之一。