天天看点

像学R一样学Python数据分析之基本数据管理

两种基本数据结构

pandas具有两种主要的数据结构,一种叫做 Series, 直译就是序列, 另一种叫做 DataFrame, 直译就是数据框。

这两者与Python内置的数据结构,以及Numpy的ndarray数据结构最大的不同就在于,它们是由数据和数据标签组成。说人话就是,它们让Python成为了一个Excel。其中 DataFrane 简单理解就是多列的 Series 。

一个示例

这里用R语言实战第二版的一个案例

本人当前工作的研究主题之一是男性和女性在领导各自企业方式上的不同。典型的问题如下。
  • 处于管理岗位的男性和女性在听从上级的程度上是否有所不同?
  • 这种情况是否依国家的不同而有所不同,或者说这些由性别导致的不同是否普遍存在?
像学R一样学Python数据分析之基本数据管理

案例数据

要解决的问题如下:

  • 五个评分(q1到q5)需要组合起来,即为每位经理人生成一个平均服从程度得分。
  • 在问卷调查中,被调查者经常会跳过某些问题。例如,为4号经理人打分的上司跳过了问题4和问题5。我们需要一种处理不完整数据的方法,同时也需要将99岁这样的年龄值重编码为缺失值。
  • 一个数据集中也许会有数百个变量,但我们可能仅对其中的一些感兴趣。为了简化问题,我们往往希望创建一个只包含那些感兴趣变量的数据集。
  • 既往研究表明,领导行为可能随经理人的年龄而改变,二者存在函数关系。要检验这种观点,我们希望将当前的年龄值重编码为类别型的年龄组(例如年轻、中年、年长)。
  • 领导行为可能随时间推移而发生改变。我们可能想重点研究最近全球金融危机期间的服从行为。为了做到这一点,我们希望将研究范围限定在某一个特定时间段收集的数据上(比如,2009年1月1日到2009年12月31日)。

创建数据集

根据表格,手动创建Series,DataFrame,

from pandas import Series, DataFrame
from numpy import nan as NA
import pandas as pd
import numpy as np
# 创建Series
manager = Series([1,2,3,4,5])
country = Series(['US','US','UK','UK','UK'])
gender = Series(['M','F','F','M','F'])
age = Series([32,45,25,39,99])
q1 = Series([5,3,3,3,2])
q2 = Series([4,5,5,3,2])
q3 = Series([5,2,5,4,1])
q4 = Series([5,5,5,NA,2])
q5 = Series([5,5,2,NA,1])
# 由Series组成DataFrame
leadership = DataFrame({'manager':manager,'country':country,'gender':gender,'age':age,
'q1':q1,'q2':q2,'q3':q3,'q4':q4,'q5':q5})
           

如果数据没有写完,增加额外列,

date = Series(['10/24/08','10/28/08','10/1/08','10/12/08','5/1/09'])
# 为不存在的列赋值能够创建新的一列。
leadership['date'] = date
# 查看数据库的值
leadership.values
# 查看前后几行
leadership.head(2)
leadership.tail(2)
           

手动创建数据的情况比较少,我们用pandas自带的读取函数导入一个以制表符分隔的格式化的文本文件,然后看下数据结构。

原始数据有20列29850行,为10个对照组10个控制组在29850个基因上的表达量。

像学R一样学Python数据分析之基本数据管理
  • 读取数据,
In [1]: import pandas as pd
In [2]: data = pd.read_table("C:/Users/Xu/Desktop/Data.txt")
In [3]: type(data)
Out[3]: pandas.core.frame.DataFrame
           
  • 简单查看数据
# 数据框大小
In [4]: data.shape
Out[4]: (29849, 21)
# 前两行
In [5]: data.head(2)
Out[5]: 
  Unnamed: 0  control1  control2  control3  control4  control5  control6  \
0       A1BG  6.917468  6.308350  5.318841  5.886811  5.082975  5.629453
1   A1BG-AS1  7.862730  7.065809  6.783732  6.275773  3.063104  5.131017
           

在继续介绍数据管理前,先简单介绍一下panda的index对象。pandas使用索引对象管理轴标签(行列名),它不可被轻易修改。因为Index对象的存在,不同来源的数据能够进行对齐,还能根据索引重新排序。

  • 选取数据框元素, 就是能够提取某几行,某几列,或者某一个元素。
# 按行选取元素, 提供单个索引,或者是list
leadership.ix[1]
leadership.ix[[1,2,3]] # 或leadership.ix[1:3]
# 按列选取元素
leadership[[1,2,3]]
leadership[[1]]
leadership['q1']
leadership.ix[:,1:3] 
## 特别的,还可以根据列名,选取一个范围
leadership.ix[:,'q1':'q5']

# 按行,按列,选取局部元素, [行,列]
leadership.ix[1,2]
leadership.ix[1:2,2:3]
leadership.ix[1:3,2:4]
           

注: Python以0为基, 所以leadership.ix[[1]]选取的是第二行,并且leadership.ix[1:3]是优先根据索引名,而leadership[1:3]则是根据位置顺序。 目前来看直接用

[]

有很多小问题,所以建议都改用

ix[]

基本数据管理

下面使用的数据来自于前面导入的

data

, 模仿《R语言实战》的基本数据管理章节编排。

创建新变量

比如说新建一个总分,是q1-q5的总计

# 用到numpy的通用数学函数, 其中axis=0表示每一列的计算结果,axis=1表示所有行的运算结果
In [29]: total = np.sum(leadership.ix[:,'q1':'q5'], axis=1)
In [30]: total
Out[30]: 
0    24.0
1    20.0
2    20.0
3    10.0
4     8.0
dtype: float64
## np.sum计算的时候会无视掉缺失值NA
# 可以直接增加到数据框内
leadership['total'] = np.sum(leadership.ix[:,'q1':'q5'], axis=1)
           

变量重编码

重编码涉及到同一变量和/或其他变量的现有值创建新值的过程。比如说,将一个连续性变量修改为一组类别值;将误编码的值替换成正确值;基于一组分数线创建一个表示及格/不及格的变量。

比如说在leadership里面的年龄中有一个是99岁,按照尝试来看就是错的,所以需要把他重编码为NA。

# 方式一
## .ix 比 .loc使用更加广泛,对于初学者来说没有差异
leadership['age'][leadership['age'] > 99] = NA
leadership.ix[:,'age'][leadership.ix[:,'age'] >= 99] = NA
leadership.loc[:,('age')][leadership['age'] == 99] = NA
# 方式二
leadership.ix[leadership['age'] > 99,'age'] = NA
leadership.loc[leadership['age'] == 99, 'age']
           

看起来第一种方法用了很多,但是都属于

chained indexing

, 直译就是连锁索引,也就是连续用了

[]

。这个其实我沿用了R语言的习惯,

leadership$age[leadership$age == 99]

,pandas在处理

chained indexing

如果发现存在赋值现象,就会报错或者警告。

问题来自于底层Python代码处理

chained indexing

时是返回视图(views)还是复制(copy),毕竟还会导致性能上的降低。所以建议第二种。

注: 视图和复制是两个不同的概念,如果你将视图赋值给新变量,对新变量的操作会影响到原始数据,而如果将原始数据的一个复制赋值给新变量,那么对新变量的操作就与原始数据无关。

下一步,我们还可以把大于75定义为older, 55和75间定义为midlle aged, 小于55则是young。

leadership['agecat'] = np.where(leadership['age'] > 75, 'Elder', np.where(np.logical_and(leadership['age']<=75, leadership['age'] >= 45), "Middle Aged", "Young" ))
           

这里用到Numpy的人二元ufunc中的元素级比较运算, np.where,np.logical_and。如果用np.greater和np.less,会遇到报错,这是因为存在缺失值。

变量重命名

如果对现有的名字不满意,可以对行名(index),列名(columns)进行修改

# 先查看列名
leadership.columns
# 函数采用rename, 参数为columns, index,可以用字典指定置换映射
leadership = leadership.rename(columns={'q1': 'item1','q2':'item2','q3':'item3','q4':'item4','q5':'item5'})

           

缺失值处理

真实世界的数据有可能存在残缺,在pandas中庸NaN表示缺失或NA值,用isnull和notnull函数进行检测

# 查看是否有缺失
pd.isnull(leadership)
pd.notnull(leadership)
## 或
leadership.isnull()
leadership.notnull()
           

缺失值的处理方法简单分为两种,一种是过滤,

dropna

或者是填充,

fillna

如果缺失部分较少,剔除后对结果没有影响,采用第一种,否则用第二种。

# 默认是axis=0, how='any',也就是提出有一个是NA的行
newdata = leadership.dropna()
# 按列剔除
leadership.dropna(axis=1)
# 仅剔除都是NA所在行
leadership.dropna(axis=1, how='all')
# inplace表示是否原始数据上操作
leadership.dropna(axis=1, how='all', inplace=True)
           

关于插值,比较复杂,以后讲解

日期值

Python标准库自带日期(date)和时间(time)数据的数据类型,主要用的是dateime, time, calenda模块,在其中

datetime.datetime

是用得最多数据类型。pandas用

to_datetime

解析不同日期的表达方式。

# pd.to_datetime
leadership['date'] = pd.to_datetime(leadership['date'])
           

更多和日期值相关的内容留待时间序列部分介绍。

类型转换

变量可以用

isstance

进行判断, 判断所属对象

isinstance(leadership, DataFrame)
# True
           

数据结构之间的转变,则直接用对应的构建函数即可

isinstance(leadership['item5'], Series)
# True
np.array(leadership['item5'])
# array([5, 5, 2, 1], dtype=int64)
           

查看数据类型则可以用

.dytpe

leadership['date'].dtype
# dtype('<M8[ns]')
           

而数据类型转换可以用

.astype

完成

leadership['item5'].dtype
# dtype('float64')
leadership['item5'] = leadership['item5'].astype(np.int64)
           

数据排序

pandas排序可以根据索引(by index),也可以根据数值(by values)

如果根据索引,分为行名(axis=0)或者是列名(axis=1)

## 首先用reindex打乱顺序
unsorted = leadership.reindex(index=[4,2,1,3,0])
## 用sort_index()排序,参数有ascending,inplace, axis
resorted = leadership.sort_index()
           

如果根据数值, 可以提供多个列,然后指定每列的升降序

leadership.sort_values(by=['item1','item2'], ascending=[False,True])
           

数据集的合并

数据集的合并分为添加列或是添加行。pandas具备按轴(行或列)自动或显式数据对其功能。并且底层是C编写,所以处理合并速度极快。以

官方文档

为例, 主要回到两个函数

pd.concat

pd.append

pd.merge

  • pd.merge 可以根据一个或多个键将不同的DataFrame中的行链接起来,类似于R的merge,但是速度更快
  • pd.concat 可以根据一条轴将多个对象堆叠在一起。

对于

pd.merge

而言,如果用过R语言的merge或者是SQL等关系型数据库的连接操作,基本上能很快理解。

df1 = DataFrame({'key':['b','b','a','c','a','a','b'], 'data1':range(7)})
df2 = DataFrame({'key':['a','b','d'], 'data2':range(3)})
# 没有显示指明键
pd.merge(df1,df2)
# 使用on,进行指明,如果左右不同,则需要用left_on, right_on指定
pd.merge(df1, df2, on='key')
           

对于数据库类型,有4种连接方式要注意,inner, outer, left, right。看下图进行了。

像学R一样学Python数据分析之基本数据管理

merge

而另一类

pd.concat

则是根据轴的标签进行合并。

s1 = Series([0,1], index=['a','b'])
s2 = Series([2,3,4], index=['c','d','e'])
s3 = Series([5,6], index=['f','g'])
pd.concat([s1,s2,s3], axis=1)
           

合并操作可以用来完成生信编程直播第四题:多个同样的行列式文件合并起来

第一步,下载操作数据,并解压

wget ftp://ftp.ncbi.nlm.nih.gov/geo/series/GSE48nnn/GSE48213/suppl/GSE48213_RAW.tar
tar -x GSE48213_RAW.tar
gzip -d *.gz
mkidr GSE48213_RAW
mv *.txtGSE48213_RAW
           

第二步,合并数据

使用

pd.merge

的代码搬运自生信技能树用户end2end

import pandas
import os
name_list=os.listdir("GSE48213_RAW")
fram_list=[pd.read_table("GSE48213_RAW/%s"%name) for name in name_list]
fram=fram_list[0]
for i in range(1,len(fram_list)):
    fram=pandas.merge(fram,fram_list[i])
fram.to_csv("result.csv",index=False)
           

如果在读取表格的时候设置基因名为轴索引(行名),那么就可以用

pd.concat

import pandas as pd
import os
name_list=os.listdir("GSE48213_RAW")
each = [pd.read_table("GSE48213_RAW/%s"%name,header=0,index_col=0) for name in name_list]
total  = pd.concat(each, axis=1)
total.to_csv("result.csv",index=True)
           

数据集取子集

这部分内容在前面有所提及,这里在基础上继续介绍

  • 选入(保留)变量,可以认为是选择列

    选入变量,可以通过

    DataFrame.ix[行索引, 列索引]

    这样记号来访问
new_data =  leadership.ix[:,6:10]
           

其中

:,

表示选取所有行。

如果是直接选取某一个列,pandas用

.

对应R的

$

leadership.date
0   2008-10-24
1   2008-10-28
2   2008-10-01
4   2009-05-01
Name: date, dtype: datetime64[ns]
           
  • 剔除(丢弃)变量

    比如说我们想剔除q3和q4两列

myvars = leadership.columns.isin(['item4','item5'])
new_data = leadership.ix[:,np.logical_not(myvars)]
           
  • 选入观测

    选入或剔除观测(行)通常是成功的数据准备和数据分析的一个关键方面。比如说让我们选取30岁以上的男性

leadership.ix[np.logical_and(leadership.gender == 'M', leadership.age > 30),:]
           

你会感觉这样写代码太长了,我们需要一个类似R的subset的函数,在pandas对应的是query.

leadership.query('age <35 and gender == "M"')
           
  • 随机抽样

    pandas提供了sample方法用于对样本进行抽样。

leadership.sample(n=None, frac=None, replace=False, weights=None, random_state=None, axis=None)
# replace表示是否放回。
# 可以按数量
leadership.sample(n=2, replace=True)
# 可以按照比例
leadership.sample(frac=0.5, replace=True)
           

小结

本文讲解了大量的基础知识。我们以R语言实战的一个数据为例,讲解了如何在Python如何创建一个DataFrame对象(手动或导入),然后根据已有变量创建新变量,对变量重编码,重命名变量。之后是缺失数据的处理,对于pandas,这部分介绍用dropna按行或按列丢弃,然后是日期值部分。 关于数据转换,分为数据结构和数据类型两部分。之后介绍了数据合并,并且以表达量矩阵合并为例实际操作,最后是数据取子集和抽样。

当我们学会基本的数据处理之后,我们接着就可以根据不计其数函数进行更高级的操作。

当你看完后续的部分,你就能掌握复杂数据集的多数工具。无论你走到哪里,都将成为数据分析师艳羡的人物!