动手学数据分析(1)——数据加载与探索性分析(EDA)

本文为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参数以实现数据的逐块读取,该参数使得函数通过分多次将文件数据读入内存,降低了内存占用。

1
2
3
4
5
6
# 逐块读取
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=0index='index_col_name'设定。
  • 也可以在后期通过方法df.set_index()df.rename(colomns={})或借助属性df.columnsdf.index暴力修改

Tips:

  • 当使用read_csv()的names参数修改列名时,其实质是在原表基础上加上给定的列名,此时header会取Null;若不使用names参数,header默认取0。因此,设定列名时,应当使用header=0来表明原数据有列名,且位于第一行,这样才能实现列名的替换。
  • df.set_index()中包含drop参数,该参数应设定为True,表示删除现有索引列,否则当前索引行将变为普通列加入现有DataFrame
1
2
3
4
5
6
7
8
9
# 方法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 参数,相当于此列没有列名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
## 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)

可以添加indexcolumns参数,不附带index参数则索引默认为自然数序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 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列:

1
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有自带的访问器操作,lociloc

iloc基于位置(数字索引)选择,通过其在 DataFrame 中的数字排位进行访问。

loc 则通过自定义标签进行提取,该方法聚焦于数据的标签(索引)而不是位置(数字索引)。

1
2
3
4
5
6
7
8
9
10
11
12
13
## 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']]

可以使用负数来进行选择:

1
2
# 选择倒数五行的内容
df.iloc[-5:]

Notes:

  • 二者的使用方法都是使用中括号[]而不是小括号()

  • lociloc 均采用了先选行后选列的语法,这与传统的 python 语法相反二者的区别

  • iloc 的区间满足前闭后开,而 loc则满足前闭后闭。因而当我们遇到 String 类型索引,需要按照索引进行选取内容时,我们往往是希望取出区间内所有的元素,此时更好的方法是使用 loc

特定条件筛选

loc 访问器操作和[]运算符可以根据输入的逻辑值Series来筛选显示的行,将自动从中选取逻辑值为True的行。

一些常见的逻辑相关符号和方法:

  • 用于连接多个条件的符号:使用 & 表示逻辑和,使用 | 表示逻辑或
  • df.isin([]):用于判断是否包含在XXX内,相当于SQL语言中的 WHERE … IN…
  • df.isnull():用于判断是否为空值
  • df.notnull():用于判断是否非空
1
2
3
4
# []运算符
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%的存活率。因此,性别可能也可以作为预测模型的考虑因素之一。

评论