分享

数据处理利器

 LibraryPKU 2018-12-02

想入门 Pandas,那么首先需要了解Pandas中的数据结构。因为Pandas中数据操作依赖于数据结构对象。Pandas中最常用的数据结构是 Series  DataFrame。这里可以将 Series和 DataFrame分别看作一维数组和二维数组。

Series

Series是一维标签数组,其可以存储任何数据类型,包括整数,浮点数,字符串等等。所谓标签数组,这里的标签即是指Series的索引。

import pandas as pd

s=pd.Series([5,4,3,2,1], index=['a''c''e'31])


⚠️ 创建时给定了一个列表: [5,4,3,2,1],并且通过 index 参数用于指定索引。如果仅给定列表,不指定index参数,默认索引为从0开始的数字。注意:索引标签为字符串和整数的混合类型。记住不要使用浮点数作为索引,并且尽量避免使用混合类型索引


除了使用传入列表或numpy数组之外,也可以通过字典的方式创建:

s=pd.Series({'a':5'b':4'c':3'd':2'e':1})


DataFrame

DataFrame是一种表格型数据结构,可以看作是具有行列标签的二维数组。每列可以是不同类型的数据,比如数值,字符串,逻辑值等。

DataFrame的创建有多种方式,比较常用的是通过字典的方式创建,此外,还可以给定数组,通过指定columns和index参数创建:

d1=pd.DataFrame({'one':[1,3,5], 'two':[2,4,6]})  # 不指定索引,默认仍从0开始。
d2=pd.DataFrame([[1,2],[3,4],[5,6]], columns=['one''two'], index=['a''b''c'])


简单的介绍了Series和DataFrame这两种数据结构之后,我们以全国空气质量历史数据(http://beijingair.sinaapp.com)为例,通过实际的数据处理来介绍一下常用的操作。


数据为逗号分隔的csv格式数据,数据存储如下:

数据存储形式


数据存储以逗号作为分隔符,列为: date, hour, type, 1001A, 1002A…,date和hour为时间信息列,type为对应的要素,其余的列均为站点名称。


读取数据
data = pd.read_csv('china_sites_20170101.csv', sep=',')

由于文件中存储了多行多列数据,因此,完全读取之后 data 为 DataFrame 类型。


探索性分析

查看DataFrame数据信息

data.shape 
data.ndim  # 获取数据的维度信息
data.index  # 获取索引
data.columns #获取列名


查看数据行列对象信息

data.info() 

class 'pandas.core.frame.DataFrame'>
RangeIndex:
 285 entries, 0 to 284
Columns: 1500 entries, date to 2846A
dtypes: float64(1497), int64(2), object(1)
memory usage: 3.3+ MB

上述数据中包含285行,1500列,其中type列为object,date和hour列为int64类型,其余列均为float64类型。memory表明数据总共占用了约3.3M内存。


数据统计信息

获取每一列的统计相关数据,count表示一列的行数,mean表示均值,std为标准差,minmax表示最小值和最大值,25%50%75%分别表示1/4位数,中位数和3/4位数。

data.describe()


⚠️ describte 仅统计数值型列的统计数据,对于object列,会直接忽略

这里还要注意一点:由于type列对应了不同的空气质量要素,而不同的空气质量要素具有不同的取值范围,因此在使用describe查看统计信息时,应针对不同的要素进行,这样才有具体意义,才能看出每个要素的值分布,以及确定是否存在异常值。


简单的数据查看

head 方法可以查看整个数据集的前几行信息,默认是前5行,但可以指定参数选择,与 head 对应的是 tail 可以查看对应的从末尾开始的默认5行数据。这两个方法类似linux中的 head 和 tail 命令。

data.head()
data.tail()


数据选择

简单的了解了上述信息之后,我们对不同的空气质量要素进行操作时就要涉及到数据的选择。


⚠️ Pandas官方提示:以下切片形式操作在简单的交互式数据分析时是非常友好的,但是如果应用于生产环境尽量使用优化后的一些方法:.at.iat.loc.iloc.ix等。


Pandas主要有两种数据查询选择操作:

  • 基于标签的查询

  • 基于整数的位置索引查询


Pandas在选择列时,无需使用 date[:, columns] 的形式,先使用 : 选择所有行,再指定 columns 列名称。选择1001A站点的数据。

data[['date''hour''type''1001A']]  # 获取四列所有行数据,仍为DataFrame
data[0:5# 选择所有列前5行数据,仅包括索引0-4行


超纲题:由于数据中包含了时间信息列(date和hour),为了方便操作,我们可以使用以下命令将时间列设置为索引。

date_index = pd.to_datetime(data.date.apply(lambda x: str(x)) + data.hour.apply(lambda x: '%02d'%x), format='%Y%m%d%H')
data.index = date_index
# data.drop(['date', 'hour], axis=1, inplace=True)  ## 删除 date和hour列,inplace选项直接针对原DataFrame操作


⚠️ 'date' 和'hour'都是整数,需要将这两列转换成字符串之后连接起来,连接的时候注意 date 形式是 '%Y%m%d',而 hour 转换的时候要转换成 '0d'的形式,防止数字为0-9时为单字符,然后使用 pd.to_datetime 函数转换,需要指定 format 参数,否则转换会出错。



  • 基于标签的查询 .loc

    .loc 主要基于标签进行数据选择,此外还可以使用逻辑数组。当所选择的项不存在时会诱发异常。

    • 单个标签

    • data.loc[:, '1001A'# 返回Series 注意 : 行索引,如果仅给定 data.loc['1001A'] 会出错
    • 标签数组

    • data.loc[:, ['1001A''1006A''2706A']]
    • 标签切片对象

      data.loc['2017-01-01 00:00:00':'2017-01-01 06:00:00''1001A':'1005A']  # 针对行和列均进行切片
      # data.loc[0:5, '1001A':'1005A] # 会出错

      ⚠️ 由于行索引已经转换为时间,因此此处不能使用 整数 索引。因为 .loc 只能用于行列标签索引,整数位置索引需要使用 .iloc

      针对时间索引,可以直接使用时间的方式来查询,对于包含时间信息的数据检索来说非常方便

    • 逻辑数组

    data.loc[data['type'] == 'AQI']  # 选择所有站点的AQI数据
    • 可调用函数

      即可以传入函数作给 .loc ,但函数返回结果应是有效的索引,比如标签或者逻辑数组

    def test(data, column, name):
        return data[columns] = name

    data.loc[test(data, 'type''AQI')]


  • 基于整数的位置索引查询 .iloc

    .iloc  主要是基于整数的位置索引,也可以使用逻辑数组的方式。如果索引越界会诱发IndexError错误,但切片索引允许索引越界

    • 单个整数

      data.iloc[0]  # 返回第1行的所有列,结果为Series
    • 整数数组

      data.iloc[[0,2,4,6,8], [0,1,2,3]]
    • 整数切片

      data.iloc[0:100:4]
    • 逻辑数组

      data.iloc[(data.type == 'AQI').values]  # 获取所有站点的AQI数据 

      ⚠️  由于 data.type == 'AQI' 返回的是 Series,我们只需要获取其中的值,因此指定 .values 属性。又超纲了==

    • 可调用函数

      传入可调用函数给 .iloc,函数返回值应为:单个整数,整数数组,数组切片或者逻辑数组。

    上述两种数据选择虽是基于DataFrame,但Series也支持同样的操作,以1001A 站点的AQI数据为例:

    s = data.loc[data.type == 'AQI']['1001A']


    由于Series只有一列,因此只需要对行进行索引操作即可,也支持基于标签和整数的位置索引方式。

    s.loc['2017-01-01 06:00:00':'2017-01-01 12:00:00']
    s.iloc[2:10]



重建索引

通过观察1001A站点的Series数据可以发现:某些时刻的数据缺失。对于时间序列数据而言,数据的缺失可能会导致分析时出现问题。因为,我们需要补齐所有时刻。

from datetime import datetime

date_new = pd.date_range(datetime(2017110), datetime(20171123), freq='1h')

data.reindex(date_new)  # 重新索引


缺失值

补齐所有时刻之后,我们可以查看一下数据的缺失情况:

data.isnull() # 返回逻辑DataFrame,缺失值为True,否则为False
# data.isnull().sum()  # 统计每个站点每个要素的总的缺失数

data.isnull().sum() 利用了逻辑运算时:True被视为1,False被视为0的方式。data.isnull() 相反的是 data.notnull() ,是缺失值为False,否则为True。如果想丢弃缺失值,可使用 .dropna 方法,即 data.dropna()


但对于时间序列而言,一般不选择直接丢弃缺失时刻,否则可能造成时间缺失,破坏连续性。因此,可以选择补齐数据。

data.fillna() # fillna 使用给定值和方法进行数据填补
data.interpolate() # interpolate 可以通过线性插值等方法通过插值补齐数据


统计计算

Pandas中Series和DataFrame均包含一些常用的统计计算方法,比如:

data.mean()  # 计算平均值
data.sum()   # 求和
data.std()   # 计算标准差
data.median() # 获取中位数

上述数据是2017年1月1日全国所有观测站观测的常规要素逐小时数据,上面几个统计命令均是对每个站点每个要素进行计算。此外,也可以对单个站点分时刻计算,比如:

data['1001A'].resample('6h').mean() # 针对1001A站点,进行每6小时求平均

.resample 是重采样方法,其返回一个对象,然后对此对象执行 .mean  求均值方法。 .mean  也可以换为 .sum.std 等方法。对于时间跨度比较长的数据,也可以求逐日平均,逐月平均等等DataFrame.resmaple('1d').mean(), DataFrame.resample('2m').mean()


对行或列应用函数: .apply

上面在创建时间索引时便利用了.apply 方法,对date 和 hour列分别进行了数据类型的转换,然后将两个字符串进行了连接,转换为时间。

pd.to_datetime(data.date.apply(lambda x: str(x)) + data.hour.apply(lambda x: '%02d'%x), format='%Y%m%d%H')


字符串函数

Series中提供了大量的字符串函数,可以对字符串类型的数据进行常规操作。比如想替换字符串,或者转换字符串大小写等等。

data.type.str.replace('PM2.5''PM2_5'# 或 data.type.replace('PM2.5', 'PM2_5')
data.type.str.lower()


轴转换

目前的数据存储形式是:站点作为列,每个站点的空气质量要素通过 type 列单独给定。有时候这种存储形式并不方便,我们想要为以下形式:

即获取每个站点时,可以直接获取当前站点的所有要素数据,而且时间索引也按照单个时刻排列,索引不会出现重复值,而之前的存储形式索引会出现重复。索引重复会使得某些操作出错。

我们可以通过 .pivot_table 进行转换:

data.pivot_table(index=data.index, columns=['type'])


索引仍为 data 的原索引,但对 type 列进行旋转。旋转完成之后返回的DataFrame的列为 MultiIndex。而关于 MultiIndex 的查询操作属于高级主题。

对于 MultiIndex 的操作,同样可以使用.loc 方法,并借助 .IndexSlice 进行索引。


索引切片: 可以理解成 idx 将 MultiIndex 视为一个新的 DataFrame,然后将上层索引视为行,下层索引视为列,以此来进行数据的查询。


idx = pd.IndexSlice

sub=data.loc[:, idx['1001A', ['AQI''PM10''PM2.5']]]

data.loc[:, idx['1001A', ['AQI', 'PM10', 'PM2.5']]]: 表示 data 中的所有行,idx['1001A', ['AQI', 'PM10', 'PM2.5']] 表示 data 中的指定列,如果将 idx 看作新的 DataFrame,那么'1001A'则是 idx 中的行,['AQI', 'PM10', 'PM2.5'] 则是 idx 中的列。


上述操作返回的列仍然是 MultiIndex,因为此时只有一个站点了,我们可以使用 .xs 方法将列从MultiIndex转换为Index

sub.xs('1001A', axis=1)


简单绘图

Python可视化工具概览 中我们提到过数据处理和可视化一条龙服务的Pandas,Pandas不仅可以进行数据处理工作,而且其还封装了一些绘图方法。首先导入 matplotlibseaborn,这是为了能够较好的显示图形比例。


import matplotlib.pyplot as plt
import seaborn as sns

sns.set_context('talk', font_scale=1.3)


这里我们选择单个站点数据进行画图:

sub = data.loc[:, idx['1001A', :]].xs('1001A', axis=1)

fig, ax = plt.subplots(figsize=(169))
sub.plot.box(ax=ax)


箱线图

上图可以看出:不同的要素其值所在范围是不同的,在探索性分析时应分开分析。


除了箱线图之外,Pandas还可以绘制折线图,条形图,饼图,密度分布等。这在数据分析时是比较方便的,但在图形美化或其他图形绘制还需要借助其他工具,比如统计绘图Seaborn更胜一筹。看这里 >>> Python简单高效的可视化神器——Seaborn


后面会继续介绍关于pandas的更多技巧和高级操作。



数据: https://pan.baidu.com/s/1whja5oxfMniOGZP3WrBC_A


- End -

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多