想入门 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', 3, 1])
⚠️ 创建时给定了一个列表: [5,4,3,2,1],并且通过 index 参数用于指定索引。如果仅给定列表,不指定index参数,默认索引为从0开始的数字。注意:索引标签为字符串和整数的混合类型。记住不要使用浮点数作为索引,并且尽量避免使用混合类型索引。
除了使用传入列表或numpy数组之外,也可以通过字典的方式创建:
s=pd.Series({'a':5, 'b':4, 'c':3, 'd':2, 'e':1})
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为标准差,min和max表示最小值和最大值,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数据
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:10, 0: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(2017, 1, 1, 0), datetime(2017, 1, 1, 23), 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不仅可以进行数据处理工作,而且其还封装了一些绘图方法。首先导入 matplotlib
和 seaborn
,这是为了能够较好的显示图形比例。
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=(16, 9))
sub.plot.box(ax=ax)
箱线图
上图可以看出:不同的要素其值所在范围是不同的,在探索性分析时应分开分析。
除了箱线图之外,Pandas还可以绘制折线图,条形图,饼图,密度分布等。这在数据分析时是比较方便的,但在图形美化或其他图形绘制还需要借助其他工具,比如统计绘图Seaborn更胜一筹。看这里 >>> Python简单高效的可视化神器——Seaborn
后面会继续介绍关于pandas的更多技巧和高级操作。
数据: https://pan.baidu.com/s/1whja5oxfMniOGZP3WrBC_A
- End -
本文由MeteoAI原创首发,未经允许禁止转载!