文章导读
- 课程难度:★★☆☆☆
- 重要度:★★★★★
- 预计学习时间:40分钟
- 简介:本节主要讲解了python数据分析时常用的清洗数据与数据预处理的操作,包括:(1)基于去除重复值、替换特定值、查找并处理缺失值的异常数据处理;(2)基于构建时间对象、运算时间对象、时间对象与字符串间的转换以及构建时间序列的时间数据处理。
- 重点涉及的函数和内容:(1).drop_duplicates()、.replace()、.isnull().any()、.isnull().all()、.fillna()、.dropna()、datetime.date() 、datetime.time()、datetime.datetime()、
datetime.fromtimestamp()、time.mktime()、.strftime()、datetime.datetime.strptime()、pd.date_range();
(2)datetime与时间戳格式间的转换;(3)时间对象与字符串间的转换。
一、异常值处理
对于一组数据而言,我们拿到手的时候一般不会是一份美好的数据供我们直接操作。最普遍的问题是异常值、重复值、缺失值的处理,这个问题实际上许多时候我们会自己通过自己设置程序进行自定义的处理。
在这里就介绍一些常用的方法。我们打开读取data_test数据:
import pandas as pd
frame = pd.read_csv('lesson8\\data_test.csv')
1、去除重复值:.drop_duplicates()
我们首先需要删除掉重复的数据,接着对异常值进行一定处理。首先是重复项的删除,我们用.drop_duplicates()
函数,这是DataFrame的成员方法。它带三个参数:
1、subset:指示一个列组,以这个列组作为唯一值标记进行去重,默认是按全部列,即剔除掉全部列完全重复的值。
2、keep:指示对保存重复值中的哪一个。为`'first'`时保存第一个重复值,为`'last'`时保存最后一个重复值,而为`False`时删除所有重复值,默认值为`frist`。
3、inplace:与此前见过的`inplace`参数相同,`True`指示直接在原有DataFrame上修改(无返回值),而`False`指示返回一个新的dataframe,默认值为`False`。
我们这里,没有重复的行,而是有重复的列,但是我们可以将列转置,将列变成行之后再去重,去重后再转置回来:
frame_tmp = frame.T
frame_tmp.drop_duplicates(inplace = True)
frame_tmp.T
2、替换特定值:.replace()
我们也可以用.replace()
函数进行替换,在此对这个函数进行讲解。它主要有四个参数:
1、to_replace:指示待替换的值,与value一同需要进行仔细讲解。
2、value:指示用来替换的值。
3、inplace:与此前相同,指示是否直接在原DataFrame上修改。
4、limit:指示最多替换几个对象,
先说最简单的调用方式,将一个值填充为另一个值,也可以将一个列表填充为一个值:
frame.replace(0, np.nan)
frame.replace([0, 0.14], np.nan)
同时,也可以将一组值填充为另一组值,一种方法是传递两个等长的数组。另一种方法是传递一个字典,指示每个值的替换值,这时不要传递第二个参数:
frame.replace([0, 0.14], [np.nan, -1])
frame.replace({0 : np.nan, 0.14 : -1})
最后还有一种方式,也是传递一个参数,键为每个列的列名,值为一个字典,这个字典中的键是该列中的被替换值,值是替换值:
frame.replace({'210X1':{0 : np.nan},
'210X3':{0.14 : -1},
'210X9':{0.06 : 50, -0.13 : -50}
})
这个.replace()
函数不止可以应用到DataFrame上,我们也可以对series进行replace
操作。例如这里对Series进行处理,方法如下:
series = frame['210X3']
series.replace({0 : np.nan, 0.14 : -1})
字符串同样有一个.replace()
函数,但是字符串做替换只能一次替换一个值,原因我觉得部分可能是如果传递字典,同时要替换一个字符串和它的子串可能会造成混乱:
string = 'This is a fine day'
string.replace('is' , '*@&')
输出结果如下:
'Th*@& *@& a fine day'
3、查找缺失值
接上面的方法,我们可以用所有的0值全部替换为缺失值nan
:
frame.replace(0, np.nan, inplace = True)
在这里我们分析缺失值时,介绍.isnull()
、.any()
和.all()
函数。
(1).isnull()
我们用.isnull()
函数查看缺失值(注意空字符串或者0仍然解读为True
):
frame.isnull()
isnull
是对于每个元素进行一次判断,如果某个值是缺失值就返回True
,如果不是缺失值则返回False
,最终返回的是一个等大小的DataFrame.
(2).any()
我们要想查看各个列是否有缺失值,可以对.isnll()
之后的结果使用.any()
方法
frame.isnull().any()
这个函数的机理是:.any()
函数返回的是是否一个序列Series存在至少一个True
(3).all()
想查看各个列是否全为空,可以对.isnll()
之后的结果使用.all()
方法。
frame.isnull().all()
这个函数的机理是:.all()
函数返回的是是否一个序列Series全是True
。
4、填充与删除缺失值
我们使用replace
函数替换异常值为缺失值nan
,接着利用.any()
函数判定出各个列是否存在缺失值。接下来就是对于缺失值进行处理了。一般而言,有填充和删除两种操作,分别对应.fillna()
和.dropna()
函数。
(1)填充 .fillna()
.fillna()
函数将缺失值nan
填充为一个新的值,这个函数同样支持inplace
和limit
参数,这里不再叙述。填充的第一种方法是值填充。比较粗糙的是将所有的缺失值填成某个固定值,但显然,在各列值数据分布显著不同的时候,这种填充方式基本不能满足需要:
frame.fillna(100)
相对实用的方法是:对每列用均值填充缺失值
rame.fillna(frame.mean()) # 或是frame.fillna(np.mean(frame))
(2)删除 .dropna()
第二种处理空值的方式是删除带了空值的样本,这里应用.dropna()
函数。这个函数直接删除掉存在空值的样本,这个函数有如下的参数可供应用:
axis:默认为0,为0时指示按行删除样本数据,为1时指示按列删除样本数据。
how:默认为any,为any时删除掉任何存在空值的数据,为all时指示仅删除全为空值的数据。
thresh:指示一行/一列中至少要有n个非空值才能被保存。
subset:指示对某个列组(按行删空值)或某个行组(按列删空值)删,默认是对所有列/行进行空值的判断及样本删除,即任何一列/一行存在空值就删除这个样本。
inplace:和之前相同,指示是否在原数据上进行修改。
执行如下的代码:
frame.dropna(axis = 0)
frame.dropna(axis = 1)
frame.dropna(axis = 0, how = 'all')
frame.dropna(axis = 0, how = 'all', thresh = 6)
frame.dropna(axis = 0, subset = ['210X10'])
对于异常值处理,我们讲了删除重复值、替换异常值、判断dataframe中的缺失值情况,并对缺失值进行填充或是删除。但实际上,异常值的问题远远比我们想象的复杂,因此经常会需要我们利用手工方式进行异常值处理的规则设置。所以在需要的时候,就只管写一个逻辑清晰但是可能相对复杂的程序吧,不用拘泥在一定要调用简洁方法这一点上。
二、时间数据处理
如下的时间处理过程主要基于datetime
包,用时间戳的时候也涉及到了time
包。datetime
包本身下属有date
类和time
类,分别处理日期和时间,也有datetime
类,能同时处理日期时间。日期对象一般指date
,即年月日,时间对象一般指time
,即时分秒,日期时间对象则指datetime
,即年月日时分秒。我们先将要用的包引用进来:
import pandas as pd
import datetime
import time
首先简单介绍两种时间类型数据。第一种是如下的一个结构体,包含的七个数字分别是年、月、日、时、分、秒、微秒,注意没有毫秒,一百万微秒等于一秒。第二种是时间戳,返回的是1970年01月01日00时00分00秒(格林尼治时间)至今度过的总秒数:
datetime.datetime.now() # 时间数据结构体
time.time() # 自1970年1月1日起度过的总时间
输出结果如下:
datetime.datetime(2019, 3, 7, 11, 11, 21, 677934)
1551928281.9009302
1、时间对象的构建
(1)日期对象 datetime.date()
我们用datetime.date()
函数来构建一个日期对象。对于年月日,我们必须全部传递。在这个日期对象构建完毕后,它包含的年、月、日信息,可以分别通过它的.year
、.month
、.day
属性访问:
t_date = datetime.date(2018, 10, 1)
t_date.year, t_date.month, t_date.day
输出结果如下:
2018 10 1
(2)时间对象 datetime.time()
我们用datetime.time()
函数来构建一个时间对象,时、分、秒需要按先后顺序传入,但除了小时要传之外,后面的都可以省略。在这个时间对象构建完毕之后,可以通过.hour
、.minute
、.second
、.microsecond
访问这个时间对象的小时、分钟、秒和微秒属性:
t_time = datetime.time(9, 30)
t_time.hour, t_time.minute, t_time.second, t_time.microsecond
输出结果如下:
9 30 0 0
(3)日期时间对象 datetime.datetime()
我们用datetime.datetime
构建一个日期时间对象,直接可以认为是年月日 + 时分秒的结果。年月日必须定义,之后可以只定义时,分秒等可以不用定义。在一个日期时间对象构建完毕后,我们同样可以访问这个日期时间对象的各个属性来访问其各个时间值,属性名称和之前的属性名称对应相等:
t_datetime = datetime.datetime(2018, 10, 1, 9, 30, 0)
t_datetime.year, t_datetime.month, t_datetime.day, \
t_datetime.hour, t_datetime.minute, t_datetime.second, t_datetime.microsecond
输出结果如下:
2018 10 1 9 30 0 0
在构建了一个datetime
对象之后,可以通过.date()
函数与.time()
函数分别访问其date
和time
部分。日期对象date
和时间对象time
也可以通过.combine()
函数拼接形成一个日期时间对象datetime
:
t_datetime.date()
t_datetime.time()
datetime.datetime.combine(t_date,t_time)
输出结果如下:
datetime.date(2018, 10, 1)
datetime.time(9, 30)
datetime.datetime(2018, 10, 1, 9, 30)
另外,这些时间工具提供了一些访问当前时间的方法。time.time()
可以访问当前时间戳,date.today()
可访问当前的日期,datetime.now()
可访问当前的日期及时间:
time.time()
datetime.date.today()
datetime.datetime.now()
输出结果如下:
1551929427.5284493
datetime.date(2019, 3, 7)
datetime.datetime(2019, 3, 7, 11, 30, 28, 127012)
2、时间对象的运算
(1)datetime与时间戳格式的转换:datetime.fromtimestamp()
datetime
对象可以与时间戳相互转换。利用datetime.fromtimestamp()
函数将时间戳转成日期时间对象,利用time.mktime()
将日期时间对象转成时间戳:
t_timestrap = time.time()
datetime.datetime.fromtimestamp(t_timestrap)
t_datetime = datetime.datetime(2018, 10, 1, 9, 30, 0)
time.mktime(t_datetime.timetuple())
输出结果如下:
datetime.datetime(2019, 3, 7, 11, 29, 28, 32347)
1538357400.0
(2)时间的比较
两个datetime
数据之间可以直接用比较运算符进行比较,也即时间在这里被赋予了大小关系:
t_datetime1 = datetime.datetime(2018, 10, 1, 9, 30, 0, 200000)
t_datetime2 = datetime.datetime(2019, 10, 2, 11, 20, 50, 400000)
t_datetime2 > t_datetime1
输出结果如下:
True
(3)计算时间差
如果想要计算一个时间对象与另一个时间对象的时间差,我们可以直接做减法,减的结果是一个datetime.timedelta
类对象。这个对象访问时会输出日数、秒数、和毫秒数:
t_datetime2 - t_datetime1
输出结果如下:
datetime.timedelta(366, 6650, 200000)
我们可以通过.days
、.seconds
、.microseconds
属性访问差的日数、秒数、和毫秒数。但这三个属性并没有告诉我们总的所差的时间,计算这个值用timedelta
对象的成员函数.total_seconds()
,这会返回所差的秒数:
t_delta = t_datetime2 - t_datetime1
t_delta.days, t_delta.seconds, t_delta.microseconds
t_delta.total_second()
输出结果如下:
366 6650 200000
31629050.2
如果我们想给出一个固定的时间差,并利用它调整现有时间,可以自己手动构建一个timedelta
对象,用于时间的加减。构建timedelta
对象利用datetime.timedelta()
函数,最多传递七个参数,按顺序传的时候,分别是日、秒、微秒、毫秒、分钟、小时、周。不过还是建议传关键字参数:
t_timedelta = datetime.timedelta(15, 20, 10000, 500, 0, 1, 1)
t_timedelta = datetime.timedelta(days = 15, seconds = 20, microseconds = 10000, \
milliseconds = 500, minutes = 0, hours = 1, weeks = 1)
t_datetime = datetime.datetime(2018, 10, 15, 11, 20, 50)
t_datetime + t_timedelta
输出结果如下:
datetime.datetime(2018, 11, 6, 12, 21, 10, 510000)
3、时间对象与字符串的转换
因为时间型数据不能直接读取,我们一般读取的都是表征时间的字符串,所以我们需要将字符串转换为时间对象。
用时间对象的成员函数.strftime()
将一个时间对象建立成一个字符串,用类函数datetime.datetime.strpttime()
将一个字符串建立成一个时间对象。我们常用的包括年%y
(或Y,前者为2位数年份),月%m,
日%d
,时%H
(或I,表示12小时制),分%M
,秒%S
。此外,尚有一些其他参数,可参考下表:
格式编码 | 含义描述 | 格式编码 | 含义描述 |
---|---|---|---|
%y | 两位数年份(00-99) | %c | 本地相应日期和时间表示 |
%Y | 四位数年份(0000-9999) | %j | 年内的一天(001-366) |
%m | 月份(01-12) | %p | 本地AM或PM表示 |
%d | 月份中的一天(01-31) | %U | 一年中的星期数(00-53) |
%H24 | 小时制小时数(00-23) | %w | 星期(0-6) |
%I12 | 小时制小时数(01-12) | %W | 一年中的星期数(00-53) |
%M | 分钟数(00-59) | %x | 本地相应的日期表示 |
%S | 秒数(00-59) | %X | 本地相应的时间表示 |
%a | 本地简化星期名称 | %Z | 当前时区 |
%A | 本地完整星期名称 | %% | %号本身 |
%b | 本地简化月份名称 | %B | 本地完整月份名称 |
注:在.strftime()
和strpttime()
里,一周的开始是周日而不是周一
在此,利用.strftime()
函数尝试构建几个时间字符串如下,其中引用的是这个日期时间对象的方法。本函数不支持中文:
t_datetime = datetime.datetime(2018, 10, 15, 21, 30, 15)
t_datetime.strftime('%Y-%m-%d %H:%M:%S')
t_datetime.strftime('%Y-%m-%d %I:%M:%S %p')
t_datetime.strftime('%Y-%m-%d(%a) %H:%M:%S %p')
输出结果如下:
2018-10-15 21:30:15
2018-10-15 09:30:15 PM
2018-10-15(Mon) 21:30:15 PM
而反过来,如果我们想要将一个字符串读取成为时间格式的数据,采用的strptime()
方法,和字符串接收的格式是类似的。我们构建几个时间对象如下,不过这里用的是类函数:
t_str1 = '2018-10-15 21:30:15'
t_str2 = '2018-10-15 09:30:15 PM'
t_str3 = '2018-10-15(Mon) 21:30:15 PM'
datetime.datetime.strptime(t_str1, '%Y-%m-%d %H:%M:%S')
datetime.datetime.strptime(t_str2, '%Y-%m-%d %I:%M:%S %p')
datetime.datetime.strptime(t_str3, '%Y-%m-%d(%a) %H:%M:%S %p')
输出结果如下:
datetime.datetime(2018, 10, 15, 21, 30, 15)
datetime.datetime(2018, 10, 15, 21, 30, 15)
datetime.datetime(2018, 10, 15, 21, 30, 15)
4、构建时间序列
(1)pd.date_range()
最后,我们给一个起始和终止的时间,或是给一个开始时间和序列长度,利用pd.date_range()
函数能自动构建出一串连续的时间序列。我们设置起始时间、终止时间和序列长度如下:
t_start = datetime.datetime(2018, 10, 1)
t_end = datetime.datetime(2018, 10, 31)
t_periods = 15
对于pd.date_range()
函数,我们必须给出的是一个时间的起始点,之后需要传入一个序列长度,或是传入一个结束时间,二者给出一个即可。参数顺序为(start,end,periods)
, 不过传递关键字参数更为直观,例如如下的调用方式:
pd.date_range(start = t_start, end = t_end)
pd.date_range(start = t_start, periods = t_periods)
输出结果如下:
DatetimeIndex(['2018-10-01', '2018-10-02', '2018-10-03', '2018-10-04',
'2018-10-05', '2018-10-06', '2018-10-07', '2018-10-08',
'2018-10-09', '2018-10-10', '2018-10-11', '2018-10-12',
'2018-10-13', '2018-10-14', '2018-10-15', '2018-10-16',
'2018-10-17', '2018-10-18', '2018-10-19', '2018-10-20',
'2018-10-21', '2018-10-22', '2018-10-23', '2018-10-24',
'2018-10-25', '2018-10-26', '2018-10-27', '2018-10-28',
'2018-10-29', '2018-10-30', '2018-10-31'],
dtype='datetime64[ns]', freq='D')
(2)pd.bdate_range()
pd.bdate_range()
函数返回的是周一至周五的时间序列,周末直接跳过。它实际上是pd.date_range()
函数的一个包装,指定了里面的一个叫做freq
的参数。随着这个参数的不同,能实现按照不同的规则进行时间序列的生成,例如说,我们传入一个B
,就实现了bdate_range
的效果。传入W
表示周,传入M
表示月尾:
pd.bdate_range(start = t_start, end = t_end)
pd.bdate_range(start = t_start, periods = t_periods)
pd.date_range(start = t_start, periods = t_periods, freq = 'W')
pd.date_range(start = t_start, periods = t_periods, freq = 'M')
输出结果如下:
DatetimeIndex(['2018-10-01', '2018-10-02', '2018-10-03', '2018-10-04',
'2018-10-05', '2018-10-08', '2018-10-09', '2018-10-10',
'2018-10-11', '2018-10-12', '2018-10-15', '2018-10-16',
'2018-10-17', '2018-10-18', '2018-10-19', '2018-10-22',
'2018-10-23', '2018-10-24', '2018-10-25', '2018-10-26',
'2018-10-29', '2018-10-30', '2018-10-31'],
dtype='datetime64[ns]', freq='B')
DatetimeIndex(['2018-10-01', '2018-10-02', '2018-10-03', '2018-10-04',
'2018-10-05', '2018-10-08', '2018-10-09', '2018-10-10',
'2018-10-11', '2018-10-12', '2018-10-15', '2018-10-16',
'2018-10-17', '2018-10-18', '2018-10-19'],
dtype='datetime64[ns]', freq='B')
DatetimeIndex(['2018-10-07', '2018-10-14', '2018-10-21', '2018-10-28',
'2018-11-04', '2018-11-11', '2018-11-18', '2018-11-25',
'2018-12-02', '2018-12-09', '2018-12-16', '2018-12-23',
'2018-12-30', '2019-01-06', '2019-01-13'],
dtype='datetime64[ns]', freq='W-SUN')
DatetimeIndex(['2018-10-31', '2018-11-30', '2018-12-31', '2019-01-31',
'2019-02-28', '2019-03-31', '2019-04-30', '2019-05-31',
'2019-06-30', '2019-07-31', '2019-08-31', '2019-09-30',
'2019-10-31', '2019-11-30', '2019-12-31'],
dtype='datetime64[ns]', freq='M')
freq
参数许多可选值,常利用的可参考如下表格,这些参数的名称实际上是pandas中用于构建时间序列的各类offset
对象的别名:
参数名 | 含义描述 | 参数名 | 含义描述 |
---|---|---|---|
B | 工作日 | A/Y | 年(年末为节点) |
D | 自然日 | AS/YS | 年(年初为节点) |
W | 周 | H | 小时 |
M | 月(月末为节点) | T/min | 分 |
MS | 月(月初为节点) | S | 秒 |
Q | 季度(季度末尾为节点) | QS | 季度(季度开头为节点) |
更高级的例子发生在对freq
的修饰上,如果我在freq
字符串前面带了一个数字n,表示每个n个间隔取一个值。
pd.date_range(start = t_start, periods = t_periods, freq = '3W')
pd.date_range(start = t_start, periods = t_periods, freq = '2M')
t_start_tmp = datetime.datetime(2018, 10, 1, 9, 30, 0)
pd.date_range(start = t_start_tmp, periods = t_periods, freq = '15T')
输出结果如下:
DatetimeIndex(['2018-10-07', '2018-10-28', '2018-11-18', '2018-12-09',
'2018-12-30', '2019-01-20', '2019-02-10', '2019-03-03',
'2019-03-24', '2019-04-14', '2019-05-05', '2019-05-26',
'2019-06-16', '2019-07-07', '2019-07-28'],
dtype='datetime64[ns]', freq='3W-SUN')
DatetimeIndex(['2018-10-31', '2018-12-31', '2019-02-28', '2019-04-30',
'2019-06-30', '2019-08-31', '2019-10-31', '2019-12-31',
'2020-02-29', '2020-04-30', '2020-06-30', '2020-08-31',
'2020-10-31', '2020-12-31', '2021-02-28'],
dtype='datetime64[ns]', freq='2M')
DatetimeIndex(['2018-10-01 09:30:00', '2018-10-01 09:45:00',
'2018-10-01 10:00:00', '2018-10-01 10:15:00',
'2018-10-01 10:30:00', '2018-10-01 10:45:00',
'2018-10-01 11:00:00', '2018-10-01 11:15:00',
'2018-10-01 11:30:00', '2018-10-01 11:45:00',
'2018-10-01 12:00:00', '2018-10-01 12:15:00',
'2018-10-01 12:30:00', '2018-10-01 12:45:00',
'2018-10-01 13:00:00'],
dtype='datetime64[ns]', freq='15T')
)
在商务分析上非常有价值,例如我想要统计自某年以来每季度的销售额、或是每个平日的平均销售额等等。对这个功能进行巧妙地利用,能够让自己在一些时候程序变得十分简洁(少写很多时间判断的功能)。
对于数据分析的数据预处理部分就讲解结束了,下节我们将讲解数据分析中数据运算与统计的内容。
对于缺乏Python基础的同仁,可以通过免费专栏🔥《Python学习笔记(基础)》从零开始学习Python
结语
请始终相信“Done is better than perfect”
,不要过分追求完美,即刻行动就是最好的开始, 一个模块一个模块地积累和练习,必将有所收获。
还有其他的问题欢迎在评论区留言📝!
[版权申明] 非商业目的注明出处可自由转载,转载请标明出处!!!
博客:butterfly_701c