概述
写这篇笔记的念头起于在工作、学习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初体验所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复