我是靠谱客的博主 矮小大象,最近开发中收集的这篇文章主要介绍python数据分析神器之一pandas_Python数据分析之pandas初体验,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

写这篇笔记的念头起于在工作、学习Python数据分析时,一些需要用到的功能在网络上找不到有效直观的解决方法,找到的主题相关的链接,大多数都是以R或者MATLAB为基础的。于是想要为丰富Python数据分析资料尽点绵力,方便其他像我这样有需要但又找不到现有答案的人。

本文主要陈述3个问题:按列内容筛选数据

DataFrame数据合并

DataFrame行和列的转换

此外还涉及到一丢丢的作图

0. 环境需求及源数据

0.1 环境需求

本文使用Python科学计算环境WinPython中的Jupyter Notebook,以前名为Ipython Notebook,这东西已经包含了Python解释器,所以完全可以把它当普通的Python环境来使用。同时它还自带了很多科学计算、数据挖掘和数据分析、数据处理相关的包。此外,除了可以写代码(支持语法高亮)外,它还支持Markdown写作。所以当前这篇文章也是在这Notebook里面完成的。

0.2 源数据

为了方便有需求的朋友进行体验测试,我把本文中用到的源数据文件打包放在了网络中,可点此下载。数据保存于Excel文件中,共三份文件,每个文件以其事件发生月份命名,各包含一个含有待处理数据的工作表,各包含约1000行数据。为方便后面进行陈述,我们可以先看一下数据的结构:

这是一份产品销售数据,其中A列是客户名称,B列是产品代号,C列是销售地区,D、E两列是无用的干扰数据,F列是销量。

本文以每月每个客户共卖出多少产品(销量)及销量最大的前5个客户分别是谁为中心开展数据处理工作

1. 数据规整

数据规整,也就是把源数据格式化为我们想要的、方便Python处理的格式。在本例中,有3个地方需要处理:去除无用信息。由上图中可以看出,数据表中前面5行是一些说明信息,对我们而言是没有实际用处的,并且不是结构化的

适当的填充。而A列是已经在Excel中进行了单元格合并的,这样的数据并不适合Python进行处理,我们要对它进行拆分并且进行相应的填充

增加日期信息。我们要按月份进行统计,但数据中并没有日期信息

首先先导入所需要的包并切换到工作目录下:

In [1]:

import pandas as pd

import numpy as np

import matplotlib

import os

os.chdir(r"E:TEMPFY1314")

先获取文件对象

In [2]:

files = [x for x in os.listdir() if x.endswith('xlsx')]

查看files列表是否包含了所有待处理文件

In [3]:

files

Out [3]:

['2013-12.xlsx', '2014-01.xlsx', '2014-02.xlsx', '~$FY1415_comb.xlsx']

如上,在我工作中,每次使用这样的方法来获取一个目录下的所有excel文件时,如果该目录下曾经有其它文件,而后来被删除掉,则在读取时,它依然会被读取到,可能是还留存在内存中没有被释放。这里我们要先把最后一个去掉:

In [4]:

files.pop()

Out [4]:

'~$FY1415_comb.xlsx'

然后,我们要把三个文件中的有效内容取出并合并到同一个DataFrame中,以方便处理。原理是分别读取3个文件中的有效数据到pandas的DataFrame中,再利用pandas的concat()函数进行合并。

In [5]:

# 用于保存3个DataFrame的字典

# 其键是文件名称(不包含后缀),值是对应的数据

all_dict = {}

for each_file in files:

# 获取月份——即文件名作为字典的键

key_month = each_file[:-5]

# 分别读取每个EXCEL文件

df = pd.read_excel(each_file, sheetname="Sheet1", header=5)

# 因为我们只需要查看客户和销量数据,所以其它列可以不要

# 此处取出Customer 和 Shipments两列数据放到待操作的DataFrame中

all_dict[key_month] = df.ix[:,['Customer', 'Shipments']]

# 添加日期列,命名为 Data

all_dict[key_month]['Date'] = key_month

# 填充Customer

filled_customer = all_dict[key_month]['Customer'].fillna(method='ffill')

# 用填充后的Customer列代替旧列

all_dict[key_month]['New_customer'] = filled_customer

all_dict[key_month].drop('Customer', axis=1, inplace=True)

# 把已放到字典中的3个DataFrame取出并放到列表中,方便后面进行合并

df_list = [all_dict[x] for x in all_dict.keys()]

# 合并3个DataFrame

comb_df = pd.concat(df_list)

在read_excel()中指定header=5可以略过前面的5行,将第6行作为DataFrame的头(header,其实DataFrame是没有“头”的说法的,这里是“列名称”的意思)。这样,去除无用信息这一工作就完成了。

接下来是使用DataFrame对象的ix方法进行切片,这里使用了切片而不是直接取列(df[['Customer', 'Shipments']])的原因切片比取列更快,因为取列其实是复制,返回的是DataFrmae的视图。

填充Customer列使用了pandas中Series和DataFrame的空值填充方法fillna(),指定填充方法为ffill,即向前填充。到这里,进行适当的填充这一工作也完成了。

此时,我们先查看一下合并后数据结构是怎么样的:

In [6]:

# 用DateFrame.head()方法可以查看DataFrame的前几行信息,默认为前5行(不包含表头)

comb_df.head()

Out[6]:

再看一下各列数据的类型

In [7]:

comb_df.info()

Out [7]:

Int64Index: 3245 entries, 0 to 1099

Data columns (total 3 columns):

Shipments 3207 non-null float64

Date 3245 non-null object

New_customer 3245 non-null object

dtypes: float64(1), object(2)

memory usage: 101.4+ KB

可以看到,我们添加进去的“日期”列,其实还不是日期,而是object类型。但这里并不影响我们进行分析,如果有需要,可以将它转换成真正的时间序列。具体可参考pandas文档。此时,我们的数据规整就结束了,接下来,可以开始进行处理了

2. 数据透视表

用过Excel处理数据的人应该都知道它有个强大的工具叫数据透视表,利用它可以方便快捷地按行和列来观察数据,其中的数据可以是求和或频率统计等等的结果。Pandas也有类似的功能,而且更加强大,个性化程度更高。

这里我们利用前面已经规整好的数据制作数据透视表,并最终得到销量Top 5客户及其总销量。这里有两种方法直接求得每个客户每个月的销售额,然后新增一列,其值为3个月销售额的和,再以总和列进行排序

先求每个客户在这三个月里的总销售额并排序得到Top 5客户,取出它们的值。再制作一份每个客户每月销售额的透视表,筛选出前面已得到的Top 5客户的行

下面分别进行演示

方法1:

In [8]:

pt1 = comb_df.pivot_table(index=['New_customer'], columns=['Date'], aggfunc='sum', fill_value='0')

这里用New_customer列作为索引,Date为列,汇总功能为求和sum,并使用0对空值进行填充。得到的透视表其实也还是一个DataFrame,可以先看一下它的数据结构和属性信息

In [9]:

pt1.head()

Out[9]:

In [10]:

pt1.info()

Out [10]:

Index: 25 entries, YA to WU

Data columns (total 3 columns):

(Shipments, 2013-12) 25 non-null object

(Shipments, 2014-01) 25 non-null object

(Shipments, 2014-02) 25 non-null object

dtypes: object(3)

memory usage: 800.0+ bytes

可以看到,它的列名(键)是一个个元组,所以我们在对它的列进行操作时要传入的就是元组。接下来我们新增一列,放于放置三个月销量的总和

In [11]:

pt1['Total'] = pd.to_numeric(pt1[('Shipments', '2013-12')]) + pd.to_numeric(pt1[('Shipments', '2014-02')]) + pd.to_numeric(pt1[('Shipments', '2014-01')])

由前面的属性信息可以看到,pt1的每一列是一个非空对象,所以如果我们直接拿这三列进行相加,是会出现TypeError的,所以要先用pandas的to_numeric()方法将其转换成数值,再进行求和运算。

11月30日更新:

对于求各列的和,今天学到了种更加方便的方式,不需要逐个指定列,代码如下:

pt1['Total'] = pt1.apply(lambda x: x.sum(), axis=1)

注意必须要指定axis=1以实现对列应用第1个参数中指定的函数,默认是axis=0也就是对行应用函数。

再来看一下现在的透视表是怎样的

In [13]:

pt1.head()

Out[13]:

现在已经得到了汇总信息,我们需要以它进行排序,DataFrame对象有一个sort_values()方法用于对值进行排序,默认是升序排列,可以传入一个ascending=False参数以使用降序排列

In [13]:

pt1.sort_values('Total', ascending=False, inplace=True)

此时,我们只需要取pt1的前5行,即可得到Top 5客户及其每月销量数据

In [14]:

Top5 = pt1.iloc[:5]

Top5

Out[14]:

注意这里的切片方法iloc[]和前面的ix[]不同,具体可查看pandas文档

方法2

先得到3个月的总销售额,再取出销量总额前5的客户名称

In [15]:

pt2 = comb_df.pivot_table(index=['New_customer'], aggfunc='sum', fill_value='0')

pt2.head()

Out[15]:

In [16]:

pt2.sort_values('Shipments', ascending=False, inplace=True)

top5_cs = pt2.index[:5]

top5_cs = list(top5_cs)

top5_cs

Out[16]:

['MA', 'AU', 'WA', 'CA', 'ME']

这里已经得到前5客户的名字并转为列表格式(方便后面进行筛选),然后再制作按月排列的透视表

In [17]:

pt3 = comb_df.pivot_table(index=['New_customer'], columns=['Date'], aggfunc='sum', fill_value='0')

目前pt3是以客户名称为索引,所以我们只需取出索引值等于列表top5_cs中的几行即可

In [18]:

Top_5 = pt3[pt3.index.isin(top5_cs)]

Top_5

Out[18]:

使用pandas数据对象的`isin()`方法可以很方便地根据列值进行筛选。除了像上面的对索引列进行筛选外,也可以对各数据列进行筛选。如下面的代码可以筛选出`('Shipments', '2013-12')`列中值为0的行

In [19]:

# 以下代码也等价于:pt3[pt3[('Shipments', '2013-12')] == '0'],适用于筛选少量数据的时候

pt3[pt3[('Shipments', '2013-12')].isin(['0'])]

Out[19]:

但这里应当提出的是,如果在这里直接用pt3[pt3[('Shipments', '2013-12')].isin(['176.567', '62.4604'])]来试图筛选('Shipments', '2013-12')列中值为176.567和62.4604的行是会出错的,因为这里得到的数值是具有一定的误差的(不过不用担心,这里的误差出现在小数点后面的好多位那里,通常不会影响到计算结果)。如果直接用pt.ix['AU', ('Shipments', '2013-12')]取AU行和('Shipments', '2013-12')列的值就会发现它并不完全等同于176.567。

3. 作图

得到Top 5客户及其每月销售总量数据后,我们就可以制作折线图。本例中,由于我在准备数据的时候是随机性选择的,所以导致了数据中没有一个客户是连续三个月都有销量的,也就是数据出现了断层,这样的数据就不适用于折线图了(可以用散点图)。好在pandas具有很好的数据处理功能,可以帮我们处理这个问题。可以利用pandas的填充功能对Top 5客户每月销量数据进行填充。

这里为了方便起见,我使用往后填充的方法对空值进行填充,对于('Shipmetns', '2014-02')列则只能使用向前填充的方法。但由于我们前面在做透视表时,使用了0填充空值,这时候就不能直接使用fillna()进行填充了,因为这时候Top5里面并没有NA值,所以我们要先把0替换成NA,再使用fillna()填充。

在这里我使用了第2种方法得到的Top_5进行作图,因为它没有Total列,更加方便

In [20]:

Top_5.replace('0', np.nan, inplace=True)

这时再来看看Top_5,它的0已经被替换为NaN了

In [21]:

Top_5

Out[21]:

再进行填充

In [22]:

Top_5 = Top_5.fillna(method='bfill')

Top_5 = Top_5.fillna(method='ffill')

In [23]:

Top_5

Out[23]:

DataFrame有个plot()方法可以直接利用DataFrame中的数据进行绘图,默认以索引列为横坐标,纵坐标则会根据数值范围自行选择。但如果我们在这时直接用Top_5.plot()进行绘图,会发现结果并不是我们想要的

In [24]:

%matplotlib inline

Top_5.plot()

Out[24]:

%matplotlib inline 这一行是为了绘图需要而添加的,如果不写,则运行后不会在下面显示图片

DataFrame.plot()函数是以列进行绘图的,而我目前为止也还没发现能设置按行进行绘图。所以我们必须要把DataFrame对象的行和列进行置换,然后再进行绘图

In [25]:

Top_5_stack = Top_5.stack()

转换后的结果是这样子的:

In [26]:

Top_5_stack

Out[26]:

明显依然不是我们想要的,但已经有点像了,这时候我们只需要把New_customer列转换到列标上去就行了,使用stack的逆方法unstack并指定列名(即key)即可

In [27]:

Top_5_stack = Top_5_stack.unstack('New_customer')

Top_5_stack

======================================================================

2017.3.20更新。今天在操作数据的时候,发现原来Pandas本身有提供类似的方法了,不需要通过stack再unstack,应该是我在写这篇文章的时候给忘了。在这里可以这么完成:

Top5.T

使用这个T方法即可得到上面的Top_5_stack

======================================================================

Out[27]:

然后就可以做图了

In [28]:

Top_5_stack.plot(figsize=(10,8))

Out[28]:

plot()中的figsize()接收一个元组参数,指定图的宽和高。由于数值差别有点大,所以这图不太好看。plot()还提供了一个sublots参数,用于设置是把所有列都放在一个折线图中还是分开绘图。我们来试试

In [29]:

Top_5_stack.plot(subplots=True, figsize=(8,12), sharex=False, sharey=False, title="Shipments Line Chart")

Out[29]:

array([,

,

,

,

], dtype=object)

sharex和sharey参数用于设置是否使用相同的x坐标和y坐标,默认是True,如果使用默认,则会是这样子的

In [29]:

Top_5_stack.plot(subplots=True, figsize=(8,12))

Out[29]:

array([,

,

,

,

], dtype=object)

本文到此结束

关于pandas的其它用法可查找pandas手册。而关于作图这里,我在工作中作图时还遇到了一些问题尚未找到解决方法

未解决的问题无法在折线图上显示数值

无法操作图例位置

x轴上显示的坐标被省略了一部分

最后一点在本例中没有出现,但是如果时间跨度变得稍大一点时,则会出现这样的情况。我在工作中处理的时间大概为16个月,在作图时就只显示了8个,如1、3、5、7、9。希望后面可以解决,或者有懂的同行还望告知一声

=========================================

16年12月30日更新:

今晚学到一种方法,可以解决第上面第3个问题,但是使用这种方法需要在作图时使用plt.plot()的方法,然后再进行设置。原理是先获取当前图例的坐标轴对象,再进行设置

# 获取坐标轴对象

ax = plt.gca()

ax.plot('传入数据')

# 如果是时间序列画图,还应该使用plot_date()方法

# 还可以使用ax.autofmt_xdate()方法使横坐标数值自适应。水平显示还是垂直、倾斜

# ax对象的locator_paramsx()方法可用于设置x轴的区间数量

# 下面这行代码就把x轴划分为5个区间。即类似于0, 2, 4, 6, 8, 10这样的序列值

# 把x改为y,则可对y轴进行设置

ax.locator_params('x', nbins=5)

此时可以针对ax对象进行设置坐标轴。这里不演示了,有需要的朋友可以自己去看官方文档。

此外,使用xlim()方法可以设置横坐标的起始值和终值,ylim()则用于设置纵轴。这个是可以在使用DataFrame对象的plot()方法时直接传入的:

df.plot(xlim(0, 20), ylim(0, 100))

至于第2个问题,图例位置。如果使用plt.plot()的方式进行作图,也是有一定自由度可以去设置的。即使用plt.legend()方法:

plt.legend(loc='0')

loc参数的0,表示自适应。1为右上角,2为左上角,3为左下角, 4为右下角。还有居中靠左、居中靠右等,需要查看官方文档了

2017年9月7日更新

为了避免X轴数据被简化,还可以在plot()中指定参数x_compat=True,问题迎刃而解。

最后

以上就是矮小大象为你收集整理的python数据分析神器之一pandas_Python数据分析之pandas初体验的全部内容,希望文章能够帮你解决python数据分析神器之一pandas_Python数据分析之pandas初体验所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部