python数据探索(2)—Pandas

本篇文章是python数据探索系列扫盲文章的第二篇,主要和各位朋友探讨一下pandas,后续文章会陆续介绍Matplotlib。如有疏漏不当的地方,请留言或私信我。共同进步!
第一篇的链接:
python数据探索(1)—numpy
本文参考了十分钟快速入门Pandas,建议有兴趣的朋友先去看看原文。

一、简述

在上篇讲述numpy的教程中,我们知道使用numpy也可以进行切片操作。但我们使用pandas可以更加方便地实现上文中的操作。
下面我们以一个数据集为例,简单说明一下pandas的用法。数据集为英国降雨数据
我们现在假设对该数据集一无所知,我们从头开始探索这个数据集。

二、Pandas基本使用

2.1 导入数据

第一步当然是导入数据。和numpy类似,Pandas也只需要一句话导入csv文件。

import pandas as pd
df = pd.read_csv('uk_rain_2014.csv', header=0,encoding='gbk')

其中header参数指明哪些是数据的列名。如果没有列名的话就将它设定为 None 。Pandas一般能自动识别,所以这个经常可以省略。
当我们处理含中文数据的集合时,最好加上encoding='gbk',可以避免乱码。

2.2 Pandas数据类型

Pandas 基于两种数据类型,series 和 dataframe。series 和nump数组差不多,是一种一维的数据类型,其中的每个元素都有各自的标签,标签可以是数字或者字符。dataframe 是一个二维的、表格型的数据结构。Pandas 的 dataframe 可以储存许多不同类型的数据,并且每个轴都有标签。你可以把它当作一个 series 的字典。

2.3 信息搜集

之前数据已经导入到 Pandas ,我们想看一眼数据来得到一些基本信息,以便在真正开始探索之前找到一些方向。
查看前 x 行的数据:

# Getting first x rows.
df.head(5)

我们得到的结果如下:

Water Year  Rain (mm) Oct-Sep  Outflow (m3/s) Oct-Sep  Rain (mm) Dec-Feb  \
0    1980/81               1182                    5408                292   
1    1981/82               1098                    5112                257   
2    1982/83               1156                    5701                330   
3    1983/84                993                    4265                391   
4    1984/85               1182                    5364                217   

   Outflow (m3/s) Dec-Feb  Rain (mm) Jun-Aug  Outflow (m3/s) Jun-Aug  
0                    7248                174                    2212  
1                    7316                242                    1936  
2                    8567                124                    1802  
3                    8905                141                    1078  
4                    5813                343                    4313  

一共是7列数据,我们再看看后面几行。

# Getting last x rows.
df.tail(5)

结果如下:

   Water Year  Rain (mm) Oct-Sep  Outflow (m3/s) Oct-Sep  Rain (mm) Dec-Feb  \
28    2008/09               1139                    4941                268   
29    2009/10               1103                    4738                255   
30    2010/11               1053                    4521                265   
31    2011/12               1285                    5500                339   
32    2012/13               1090                    5329                350   

    Outflow (m3/s) Dec-Feb  Rain (mm) Jun-Aug  Outflow (m3/s) Jun-Aug  
28                    6690                323                    3189  
29                    6435                244                    1958  
30                    6593                267                    2885  
31                    7630                379                    5261  
32                    9615                187                    1797  

跟 head 一样,我们只需要调用 tail 并且传入想要查看的行数即可。注意,它并不是从最后一行倒着显示的,而是按照数据原来的顺序显示。
我们可以发现列名很长,使用起来不太方便。所以我们可以修改一下列名。

# Changing column labels.
df.columns = ['water_year','rain_octsep', 'outflow_octsep',
              'rain_decfeb', 'outflow_decfeb', 'rain_junaug', 'outflow_junaug']

df.head(5)

我们发现列名改变了。

water_year  rain_octsep  outflow_octsep  rain_decfeb  outflow_decfeb  \
0    1980/81         1182            5408          292            7248   
1    1981/82         1098            5112          257            7316   
2    1982/83         1156            5701          330            8567   
3    1983/84          993            4265          391            8905   
4    1984/85         1182            5364          217            5813   

   rain_junaug  outflow_junaug  
0          174            2212  
1          242            1936  
2          124            1802  
3          141            1078  
4          343            4313  

我们通常会想知道数据的另一个特征——它有多少条记录。在 Pandas 中,一条记录对应着一行,所以我们可以对数据集调用 len 方法,它将返回数据集的总行数:

# Finding out how many rows dataset has.
len(df)

本例中结果是33。
数据集的一些基本的统计数据,在 Pandas 中我们可以用describe()获得

df.describe()
       rain_octsep  outflow_octsep  rain_decfeb  outflow_decfeb  rain_junaug  \
count    33.000000       33.000000    33.000000       33.000000    33.000000   
mean   1129.000000     5019.181818   325.363636     7926.545455   237.484848   
std     101.900074      658.587762    69.995008     1692.800049    66.167931   
min     856.000000     3479.000000   206.000000     4578.000000   103.000000   
25%    1053.000000     4506.000000   268.000000     6690.000000   193.000000   
50%    1139.000000     5112.000000   309.000000     7630.000000   229.000000   
75%    1182.000000     5497.000000   360.000000     8905.000000   280.000000   
max    1387.000000     6391.000000   484.000000    11486.000000   379.000000   

       outflow_junaug  
count       33.000000  
mean      2439.757576  
std       1025.914106  
min       1078.000000  
25%       1797.000000  
50%       2142.000000  
75%       2959.000000  
max       5261.000000 

2.4 数据筛选

下面我们讨论如何获取我们想要的数据。比如提取一整列,使用列的标签可以非常简单地做到。

# Getting a column by label
df['rain_octsep']

结果如下。当我们提取列的时候,会得到一个 series ,而不是 dataframe 。记得我们前面提到过,你可以把 dataframe 看作是一个 series 的字典,所以在抽取列的时候,我们就会得到一个 series。

0     1182
1     1098
2     1156
3      993
4     1182
5     1027
6     1151
7     1210
8      976
9     1130
10    1022

……

我们就可以像访问对象属性一样访问数据集的列——只用一个点号。如下所示,而结果不变。

# Getting a column by label using .
df.rain_octsep

之前numpy系列的文档中我们提到可以使用“布尔过滤”,在Pandas中,我们依然可以这么使用。

# Creating a series of booleans based on a conditional
df.rain_octsep < 1000 

或者这么写

# Using a series of booleans to filter
df[df.rain_octsep < 1000]

结果如下:

3     1983/84          993            4265          391            8905   
8     1988/89          976            4330          309            6465   
15    1995/96          856            3479          245            5515   

    rain_junaug  outflow_junaug  
3           141            1078  
8           200            1440  
15          172            1439  

可以通过复合条件表达式来进行过滤:

# Filtering by multiple conditionals
df[(df.rain_octsep < 1000) & (df.outflow_octsep < 4000)] # Can't use the keyword 'and'

这条代码只会返回 rain_octsep 中小于 1000 的和 outflow_octsep 中小于 4000 的记录:
注意重要的一点:这里不能用 and 关键字,因为会引发操作顺序的问题。必须用 & 和圆括号。
如果你的数据中字符串,也可以使用字符串方法来进行过滤:

# Filtering by string methods
df[df.water_year.str.startswith('199')]

必须用 .str.[string method] ,而不能直接在字符串上调用字符方法。上面的代码返回所有 90 年代的记录:

water_year  rain_octsep  outflow_octsep  rain_decfeb  outflow_decfeb  \
10    1990/91         1022            4418          305            7120   
11    1991/92         1151            4506          246            5493   
12    1992/93         1130            5246          308            8751   
13    1993/94         1162            5583          422           10109   
14    1994/95         1110            5370          484           11486   
15    1995/96          856            3479          245            5515   
16    1996/97         1047            4019          258            5770   
17    1997/98         1169            4953          341            7747   
18    1998/99         1268            5824          360            8771   
19    1999/00         1204            5665          417           10021   

    rain_junaug  outflow_junaug  
10          216            1923  
11          280            2118  
12          219            2551  
13          193            1638  
14          103            1231  
15          172            1439  
16          256            2102  
17          285            3206  
18          225            2240  
19          197            2166  

2.5 使用索引

Pandas 的行也有标签。行标签可以是基于数字的或者是标签,而且获取行数据的方法也根据标签的类型各有不同。
如果你的行标签是数字型的,你可以通过 iloc 来引用:

# Getting a row via a numerical index
df.iloc[30]

iloc 只对数字型的标签有用。它会返回给定行的 series,行中的每一列都是返回 series 的一个元素。

也许你的数据集中有年份或者年龄的列,你可能想通过这些年份或者年龄来引用行,这个时候我们就可以设置一个(或者多个)新的索引:

# Setting a new index from an existing column
df = df.set_index(['water_year'])
df.head(5)

上面的代码将 water_year 列设置为索引。注意,列的名字实际上是一个列表,虽然上面的例子中只有一个元素。如果你想设置多个索引,只需要在列表中加入列的名字即可。


image.png

上例中我们设置的索引列中都是字符型数据,这意味着我们不能继续使用 iloc 来引用,那我们用什么呢?用 loc 。

# Getting a row via a label-based index
df.loc['2000/01']

和 iloc 一样,loc 会返回你引用的列,唯一一点不同就是此时你使用的是基于字符串的引用,而不是基于数字的。
还有一个引用列的常用常用方法—— ix 。如果 loc 是基于标签的,而 iloc 是基于数字的,那 ix 是基于什么的?事实上,ix 是基于标签的查询方法,但它同时也支持数字型索引作为备选。

# Getting a row via a label-based or numerical index
df.ix['1999/00'] # Label based with numerical index fallback *Not recommended

将索引排序通常会很有用,在 Pandas 中,我们可以对 dataframe 调用 sort_index 方法进行排序。

df.sort_index(ascending=False).head(5) #inplace=True to apple the sorting in place

我的索引本来就是有序的,为了演示,我将参数 ascending 设置为 false,这样我的数据就会呈降序排列。


image.png

当你将一列设置为索引的时候,它就不再是数据的一部分了。如果你想将索引恢复为数据,调用 set_index 相反的方法 reset_index 即可:

# Returning an index to data
df = df.reset_index('water_year')
df.head(5)
image.png

2.5 操作数据集的结构

重新建立数据结构,使得数据集呈现出一种更方便并且(或者)有用的形式。
首先,是 groupby :

#Manipulating structure (groupby, unstack, pivot)
# Grouby
df.groupby(df.year // 10 *10).max()

groupby 会按照你选择的列对数据集进行分组。上例是按照年代分组。不过仅仅这样做并没有什么用,我们必须对其调用函数,比如 max 、 min 、mean 等等。例中,我们可以得到 90 年代的均值。


image.png

也可以按照多列进行分组:

# Grouping by multiple columns
decade_rain = df.groupby([df.year // 10 * 10, df.rain_octsep // 1000 * 1000])[['outflow_octsep', 'outflow_decfeb', 'outflow_junaug']].mean()
decade_rain
image.png

接下来是 unstack ,最开始可能有一些困惑,它可以将一列数据设置为列标签。最好还是看看实际的操作:

# Unstacking
decade_rain.unstack(0)

这条语句将上例中的 dataframe 转换为下面的形式。它将第 0 列,也就是 year 列设置为列的标签。


image.png

让我们再操作一次。这次使用第 1 列,也就是 rain_octsep 列:

# More unstacking
decade_rain.unstack(1)
image.png

有一个 .fillna('') 。pivot 产生了很多空的记录,也就是值为 NaN 的记录。数据集里面有很多 NaN 会很烦,所以使用了 fillna('') 。你也可以用别的别的东西,比方说 0 。我们也可以使用 dropna(how = 'any') 来删除有 NaN 的行,不过这样就把所有的数据都删掉了,所以不这样做。

2.6 合并数据集

有两个相关联的数据集,你想将它们放在一起比较或者合并它们。好的,没问题,在 Pandas 里很简单:

# Merging two datasets together
rain_jpn = pd.read_csv('jpn_rain.csv')
rain_jpn.columns = ['year', 'jpn_rainfall']

uk_jpn_rain = df.merge(rain_jpn, on='year')
uk_jpn_rain.head(5)

首先你需要通过 on 关键字来指定需要合并的列。通常你可以省略这个参数,Pandas 将会自动选择要合并的列。

如下图所示,两个数据集在年份这一类上合并了。jpn_rain 数据集只有年份和降雨量两列,通过年份列合并之后,jpn_rain 中只有降雨量那一列合并到了 UK_rain 数据集中。


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容