python-数据分析-Pandas-3、DataFrame-数据重塑

在完成数据加载之后,我们可能需要对事实表和维度表进行连接,这是对数据进行多维度拆解的基础;
我们可能从不同的数据源加载了结构相同的数据,我们需要将这些数据拼接起来;我们把这些操作统称为数据重塑。
当然,由于企业的信息化水平以及数据中台建设水平的差异,我们拿到的数据未必是质量很好的,可能还需要对数据中的缺失值、重复值、异常值进行适当的处理。
即便我们获取的数据在质量上是没有问题的,但也可能需要对数据进行一系列的预处理,才能满足我们做数据分析的需求。
接下来,我们就为大家讲解和梳理这方面的知识。

数据重塑

# -*- coding: utf-8 -*-
#数据重塑


import pandas
from sqlalchemy import create_engine

#创建连接
engine = create_engine('mysql+pymysql://root:123456@192.168.177.190:3307/demo?charset=utf8')
dept_df = pandas.read_sql_table('dept_df', con=engine, index_col='dno')
emp_df = pandas.read_sql_table('emp_df', con=engine, index_col='eno')
emp2_df = pandas.read_sql_table('emp2_df', con=engine, index_col='eno')

#使用pandas提供的concat函数实现两个或多个DataFrame的数据拼接
#拼接emp_df和emp2_df
all_emp_df = pandas.concat([emp_df, emp2_df])
# print(all_emp_df)

#使用merge函数将员工表(all_emp_df)和部门表(dept_df)的数据合并到一张表中
#1、先使用reset_index方法重新设置all_emp_df的索引
#这样eno 不再是索引而是一个普通列,reset_index方法的inplace参数设置为True表示、重置索引的操作直接在all_emp_df上执行,而不是返回修改后的新对象
all_emp_df.reset_index(inplace=True)

#通过merge函数合并数据,当然,也可以调用DataFrame对象的merge方法来达到同样的效果
#pandas.merge()函数的参数说明:
#left:左表
#right:右表
#how:连接类型,默认为inner
#on:连接条件,默认为None,表示连接条件为左表和右表的索引列相同
#left_on:左表连接条件,默认为None
#right_on:右表连接条件,默认为None
#left_index:左表连接条件是否为索引列,默认为False
#right_index:右表连接条件是否为索引列,默认为False
new_data = pandas.merge(all_emp_df, dept_df, how='inner', on='dno')
print(new_data)
'''
     eno ename   job     mgr    sal    comm  dno dname dloc
0   1359   胡一刀   销售员  3344.0   1800   200.0   30   销售部   重庆
1   3344    黄蓉  销售主管  7800.0   3000   800.0   30   销售部   重庆
2   4466   苗人凤   销售员  3344.0   2500     NaN   30   销售部   重庆
3   2056    乔峰   分析师  7800.0   5000  1500.0   20   研发部   成都
4   3088   李莫愁   设计师  2056.0   3500   800.0   20   研发部   成都
5   3211   张无忌   程序员  2056.0   3200     NaN   20   研发部   成都
6   3233   丘处机   程序员  2056.0   3400     NaN   20   研发部   成都
7   3244   欧阳锋   程序员  3088.0   3200     NaN   20   研发部   成都
8   3251   张翠山   程序员  2056.0   4000     NaN   20   研发部   成都
9   7800   张三丰    总裁     NaN   9000  1200.0   20   研发部   成都
10  9500   张三丰    总裁     NaN  50000    8000   20   研发部   成都
11  9600   王大锤   程序员  9800.0   8000     600   20   研发部   成都
12  9700   张三丰    总裁     NaN  60000    6000   20   研发部   成都
13  9800    骆昊   架构师  7800.0  30000    5000   20   研发部   成都
14  9900   陈小刀   分析师  9800.0  10000    1200   20   研发部   成都
15  3577    杨过    会计  5566.0   2200     NaN   10   会计部   北京
16  3588   朱九真    会计  5566.0   2500     NaN   10   会计部   北京
17  5234    郭靖    出纳  5566.0   2000     NaN   10   会计部   北京
18  5566   宋远桥   会计师  7800.0   4000  1000.0   10   会计部   北京
'''
#merge函数的一个参数代表合并的左表、第二个参数代表合并的右表,有SQL编程经验的同学对这两个词是不是感觉到非常亲切。
# 正如大家猜想的那样,DataFrame对象的合并跟数据库中的表连接非常类似,所以上面代码中的how代表了合并两张表的方式,
# 有left、right、inner、outer四个选项;而on则代表了基于哪个列实现表的合并,相当于 SQL 表连接中的连表条件,
# 如果左右两表对应的列列名不同,可以用left_on和right_on参数取代on参数分别进行指定。

#如果对上面的代码稍作修改,将how参数修改为'right'
new_data = pandas.merge(all_emp_df, dept_df, how='right', on='dno')
print(new_data)
'''
#运行结果比之前的输出多出了如下所示的一行,这是因为how='right'代表右外连接,
#也就意味着右表dept_df中的数据会被完整的查出来,但是在all_emp_df中又没有编号为40 部门的员工,所以对应的位置都被填入了空值。
19	NaN    NaN    NaN    NaN    NaN     NaN    40    运维部    深圳  #多出来的
'''

print('-------------------------------------------------------')

#数据的清洗
#通常,我们从 Excel、CSV 或数据库中获取到的数据并不是非常完美的,里面可能因为系统或人为的原因混入了重复值或异常值,也可能在某些字段上存在缺失值;
# 再者,DataFrame中的数据也可能存在格式不统一、量纲不统一等各种问题。因此,在开始数据分析之前,对数据进行清洗就显得特别重要。

#缺失值
#可以使用DataFrame对象的isnull或isna方法来找出数据表中的缺失值、如下:
print(emp_df.isnull())
print(emp_df.isna())
'''
      ename    job    mgr    sal   comm    dno
eno                                           
1359  False  False  False  False  False  False
2056  False  False  False  False  False  False
3088  False  False  False  False  False  False
3211  False  False  False  False  False  False
3233  False  False  False  False  False  False
3244  False  False  False  False  False  False
3251  False  False  False  False  False  False
3344  False  False  False  False  False  False
3577  False  False  False  False  False  False
3588  False  False  False  False  False  False
4466  False  False  False  False  False  False
5234  False  False  False  False  False  False
5566  False  False  False  False  False  False
7800  False  False  False  False  False  False
'''

#notnull和notna方法可以将非空的值标记为True
#删除这些缺失值,可以使用DataFrame对象的dropna方法
#该方法的axis参数可以指定沿着0轴还是1轴删除,也就是说当遇到空值时,是删除整行还是删除整列,默认是沿0轴进行删除的
print(emp_df.dropna())
#如果要沿着1轴进行删除,可以使用下面的代码。
print(emp_df.dropna(axis=1))
'''
注意:DataFrame对象的很多方法都有一个名为inplace的参数,该参数的默认值为False,表示我们的操作不会修改原来的DataFrame对象,
而是将处理后的结果通过一个新的DataFrame对象返回。如果将该参数的值设置为True,那么我们的操作就会在原来的DataFrame上面直接修改,方法的返回值为None。
简单的说,上面的操作并没有修改emp_df,而是返回了一个新的DataFrame对象
'''

#填充缺失值
#如果要填充缺失值,可以使用DataFrame对象的fillna方法,该方法的value参数可以指定填充的值,如下:
print(emp_df.fillna(value=0))

print('-------------------------------------------------------')

#重复值
#先给之前的部门表添加两行数据,让部门表中名为“研发部”和“销售部”的部门各有两个
dept_df.loc[50] = {'dname': '研发部', 'dloc': '深圳'}
dept_df.loc[60] = {'dname': '销售部', 'dloc': '长沙'}
print(dept_df)
'''
    dname dloc
dno           
10    会计部   北京
20    研发部   成都
30    销售部   重庆
40    运维部   深圳
50    研发部   深圳
60    销售部   长沙
'''
#通过DataFrame对象的duplicated方法判断是否存在重复值,该方法在不指定参数时默认判断行索引是否重复,我们也可以指定根据部门名称dname判断部门是否重复
print(dept_df.duplicated('dname'))
'''
dno
10    False
20    False
30    False
40    False
50     True
60     True
dtype: bool
'''

#50和60两个部门从部门名称上来看是重复的,如果要删除重复值,可以使用drop_duplicates方法
#该方法的keep参数可以控制在遇到重复值时,保留第一项还是保留最后一项,或者多个重复项一个都不用保留,全部删除掉。
print(dept_df.drop_duplicates('dname'))
'''
    dname dloc
dno           
10    会计部   北京
20    研发部   成都
30    销售部   重庆
40    运维部   深圳
'''
#将keep参数的值修改为last
print(dept_df.drop_duplicates('dname', keep='last'))
'''
    dname dloc
dno           
10    会计部   北京
40    运维部   深圳
50    研发部   深圳
60    销售部   长沙
'''

#使用同样的方式,我们也可以清除all_emp_df中的重复数据,
# 例如我们认定“ename”和“job”两个字段完全相同的就是重复数据,我们可以用下面的代码去除重复数据。
print(all_emp_df.drop_duplicates(['ename', 'job'], inplace=True))
'''
    dname dloc
dno           
10    会计部   北京
40    运维部   深圳
50    研发部   深圳
60    销售部   长沙
None
'''

#说明:上面的drop_duplicates方法添加了参数inplace=True,该方法不会返回新的DataFrame对象,而是在原来的DataFrame对象上直接删除
# 可以查看all_emp_df看看是不是已经移除了重复的员工数据
print(all_emp_df)
'''
     eno ename   job     mgr    sal    comm  dno
0   1359   胡一刀   销售员  3344.0   1800   200.0   30
1   2056    乔峰   分析师  7800.0   5000  1500.0   20
2   3088   李莫愁   设计师  2056.0   3500   800.0   20
3   3211   张无忌   程序员  2056.0   3200     NaN   20
4   3233   丘处机   程序员  2056.0   3400     NaN   20
5   3244   欧阳锋   程序员  3088.0   3200     NaN   20
6   3251   张翠山   程序员  2056.0   4000     NaN   20
7   3344    黄蓉  销售主管  7800.0   3000   800.0   30
8   3577    杨过    会计  5566.0   2200     NaN   10
9   3588   朱九真    会计  5566.0   2500     NaN   10
10  4466   苗人凤   销售员  3344.0   2500     NaN   30
11  5234    郭靖    出纳  5566.0   2000     NaN   10
12  5566   宋远桥   会计师  7800.0   4000  1000.0   10
13  7800   张三丰    总裁     NaN   9000  1200.0   20
15  9600   王大锤   程序员  9800.0   8000   600.0   20
17  9800    骆昊   架构师  7800.0  30000  5000.0   20
18  9900   陈小刀   分析师  9800.0  10000  1200.0   20
'''

 

预处理

对数据进行预处理也是一个很大的话题,它包含了对数据的拆解、变换、归约、离散化等操作。我们先来看看数据的拆解。如果数据表中的数据是一个时间日期,

我们通常都需要从年、季度、月、日、星期、小时、分钟等维度对其进行拆解,如果时间日期是用字符串表示的,可以先通过pandas的to_datetime函数将其处理成时间日期。

# -*- coding: utf-8 -*-
#预处理""

import pandas
import numpy

#先读取 Excel 文件,获取到一组销售数据,其中第一列就是销售日期,我们将其拆解为“月份”、“季度”和“星期”
sales_df = pandas.read_excel(
    'file/2020年销售数据.xlsx',
    # usecols指定读取的列
    usecols=['销售日期', '销售区域', '销售渠道', '品牌', '销售数量']
)
# print(sales_df.info())
'''
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1945 entries, 0 to 1944
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype         
---  ------  --------------  -----         
 0   销售日期    1945 non-null   datetime64[ns]
 1   销售区域    1945 non-null   object        
 2   销售渠道    1945 non-null   object        
 3   品牌      1945 non-null   object        
 4   销售数量    1945 non-null   int64         
dtypes: datetime64[ns](1), int64(1), object(3)
memory usage: 76.1+ KB
None
'''

month = sales_df['月份'] = sales_df['销售日期'].dt.month
quarter = sales_df['季度'] = sales_df['销售日期'].dt.quarter
week = sales_df['星期'] = sales_df['销售日期'].dt.weekday
print(sales_df)
'''
           销售日期 销售区域 销售渠道    品牌  销售数量  月份  季度  星期
0    2020-01-01   上海  拼多多   八匹马    83   1   1   2
1    2020-01-01   上海   抖音   八匹马    29   1   1   2
2    2020-01-01   上海   天猫   八匹马    85   1   1   2
3    2020-01-01   上海   天猫   八匹马    14   1   1   2
4    2020-01-01   上海   天猫   皮皮虾    61   1   1   2
...         ...  ...  ...   ...   ...  ..  ..  ..
1940 2020-12-30   北京   京东  花花姑娘    26  12   4   2
1941 2020-12-30   福建   实体   八匹马    97  12   4   2
1942 2020-12-31   福建   实体  花花姑娘    55  12   4   3
1943 2020-12-31   福建   抖音   八匹马    59  12   4   3
1944 2020-12-31   福建   天猫   八匹马    27  12   4   3
'''
#在上面的代码中,通过日期时间类型的Series对象的dt 属性,获得一个访问日期时间的对象,
# 通过该对象的year、month、quarter、hour等属性,就可以获取到年、月、季度、小时等时间信息,
# 获取到的仍然是一个Series对象,它包含了一组时间信息,所以我们通常也将这个dt属性称为“日期时间向量”。

print('=======================================================================================')
#字符串类型的数据的处理

#读取csv文件的数据
jobs_df = pandas.read_csv(
    'file/某招聘网站招聘数据.csv',
    #读取指定列的顺序
    usecols=['city', 'companyFullName', 'positionName', 'salary']
)
print(jobs_df.info())
'''
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3140 entries, 0 to 3139
Data columns (total 4 columns):
 #   Column           Non-Null Count  Dtype 
---  ------           --------------  ----- 
 0   city             3140 non-null   object
 1   companyFullName  3140 non-null   object
 2   positionName     3140 non-null   object
 3   salary           3140 non-null   object
dtypes: object(4)
memory usage: 98.2+ KB
None
'''

#查看前5条数据
print(jobs_df.head())
'''
  city companyFullName positionName   salary
0   北京  达疆网络科技(上海)有限公司        数据分析岗  15k-30k
1   北京    北京音娱时光科技有限公司         数据分析  10k-18k
2   北京   北京千喜鹤餐饮管理有限公司         数据分析  20k-30k
3   北京   吉林省海生电子商务有限公司         数据分析  33k-50k
4   北京  韦博网讯科技(北京)有限公司         数据分析  10k-15k
'''

#如果要筛选出数据分析的岗位,可以通过检查positionName字段是否包含“数据分析”这个关键词,这里需要模糊匹配,应该如何实现呢?
# 我们可以先获取positionName列,因为这个Series对象的dtype是字符串,所以可以通过str属性获取对应的字符串向量,
# 然后就可以利用我们熟悉的字符串的方法来对其进行操作

#筛选出数据分析的岗位
#jobs_df.positionName.str.contains:获取positionName列,然后检查是否包含“数据分析”这个关键词
# str.contains:检查是否包含指定字符串
jobs_df = jobs_df[jobs_df.positionName.str.contains('数据分析')]
#shape: 显示数据的形态:数据的行数和列数
print(jobs_df.shape)    #(1515, 4)

#接下来,我们还需要对salary字段进行处理,如果我们希望统计所有岗位的平均工资或每个城市的平均工资,首先需要将用范围表示的工资处理成其中间值
#说明:下面的代码通过正则表达式捕获组从字符串中抽取出两组数字,分别对应工资的下限和上限
#extract:提取字符串中的匹配项、
# jobs_df.salary.str.extract(r'(\d+)[kK]?-(\d+)[kK]?')
#
# #需要提醒大家的是,抽取出来的两列数据都是字符串类型的值,我们需要将其转换成int类型,才能计算平均值,
# # 对应的方法是DataFrame对象的applymap方法,该方法的参数是一个函数,而该函数会作用于DataFrame中的每个元素。
# # 完成这一步之后,我们就可以使用apply方法将上面的DataFrame处理成中间值,apply方法的参数也是一个函数,可以通过指定axis参数使其作用于DataFrame 对象的行或列,
# # 代码如下所示。
# temp_df = jobs_df.salary.str.extract(r'(\d+)[kK]?-(\d+)[kK]?').applymap(int)
# temp_df.apply(numpy, axis=1)

#接下来,我们可以用上面的结果替换掉原来的salary列或者增加一个新的列来表示职位对应的工资
#完整的代码如下所示。
temp_df = jobs_df.salary.str.extract(r'(\d+)[kK]?-(\d+)[kK]?').applymap(int)
jobs_df['salary'] = temp_df.apply(numpy.mean, axis=1)    #mean:求平均值 axis:0表示按列计算,1表示按行计算
print(jobs_df.head())
'''
  city companyFullName positionName  salary
0   北京  达疆网络科技(上海)有限公司        数据分析岗    22.5
1   北京    北京音娱时光科技有限公司         数据分析    14.0
2   北京   北京千喜鹤餐饮管理有限公司         数据分析    25.0
3   北京   吉林省海生电子商务有限公司         数据分析    41.5
4   北京  韦博网讯科技(北京)有限公司         数据分析    12.5

'''


#applymap和apply两个方法在数据预处理的时候经常用到,Series对象也有apply方法,也是用于数据的预处理,
# 但是DataFrame对象还有一个名为transform 的方法,也是通过传入的函数对数据进行变换,类似Series对象的map方法。
# 需要强调的是,apply方法具有归约效果的,简单的说就是能将较多的数据处理成较少的数据或一条数据;
# 而transform方法没有归约效果,只能对数据进行变换,原来有多少条数据,处理后还是有多少条数据。

 

posted @ 2024-06-10 00:26  little小新  阅读(21)  评论(0编辑  收藏  举报