导入所需的库

import numpy as np
import pandas as pd
from numpy import nan as NA

空值处理

判断是否有空值

s1 = pd.Series(['one', 'two', np.nan, 'four', np.nan])
print(s1.isnull())
'''
0 False
1 False
2 True
3 False
4 True
dtype: bool
'''

判断有几个空值(统计空值个数)

print(s1.isnull().sum())
'''
2
'''

缺失值处理

删除

源数据:

# Series
data = pd.Series([1,2,3,NA, 7, NA,9])
print(data)
'''
0 1.0
1 2.0
2 3.0
3 NaN
4 7.0
5 NaN
6 9.0
dtype: float64
'''

# DataFrame:
data1 = pd.DataFrame([[8, 8, 8], [NA, NA, NA], [NA, 4, 5], [NA, 7, 8]])
print(data1)
'''
0 1 2
0 8.0 8.0 8.0
1 NaN NaN NaN
2 NaN 4.0 5.0
3 NaN 7.0 8.0
'''

dropna() 函数:会删除所有带空值的关联数据(比如行,列)

# Series
print(data.dropna())
'''
0 1.0
1 2.0
2 3.0
4 7.0
6 9.0
dtype: float64
'''

# DataFrame
print(data1.dropna())
'''
0 1 2
0 8.0 8.0 8.0
'''
dropna() 函数中的 how=”all” 参数:只删除所有数据为空值的行或列
print(data1.dropna(how="all"))
''''
0 1 2
0 8.0 8.0 8.0
2 NaN 4.0 5.0
3 NaN 7.0 8.0
'''
dropna() 函数中的 axis=1 参数:删除空值的方向,默认为 0 ,即行, 设置为 1 则为列
print(data1.dropna(how="all", axis=1))
'''
0 1 2
0 8.0 8.0 8.0
1 NaN NaN NaN
2 NaN 4.0 5.0
3 NaN 7.0 8.0
'''
dropna() 函数中的 thresh=1 参数:设置阈值,即数据中的行至少需要保留一个非空值的数据(如果某行的数据都为空值,则删除该行)
print(data1.dropna(thresh=1))
'''
0 1 2
0 8.0 8.0 8.0
2 NaN 4.0 5.0
3 NaN 7.0 8.0
'''

填充

源数据:

df = pd.DataFrame(np.random.randn(6,4))
print(df)
'''
0 1 2 3
0 0.731079 -0.453096 0.738827 -0.178366
1 0.119566 -0.481404 2.169273 -0.794036
2 -0.044611 0.752900 0.008228 0.793652
3 0.939126 0.790010 -0.576827 -0.460536
4 -0.597286 0.264932 0.341335 -1.331357
5 0.030722 -0.224758 0.340609 -0.344692
'''

通过赋值的方式填充

df.iloc[:3,2]=NA
print(df)
'''
0 1 2 3
0 0.420970 -0.030863 NaN 0.833263
1 1.008801 -0.631710 NaN 0.506130
2 1.071905 0.401718 NaN -0.410824
3 0.343352 1.321416 2.456993 0.187760
4 0.969128 0.420402 -0.559659 -1.140751
5 -0.783568 0.691397 -2.843437 0.351345
'''

常数填充:用 0 填充

print(df.fillna(0))
'''
0 1 2 3
0 0.719469 0.098692 0.000000 1.168462
1 0.113789 0.659131 0.000000 0.647481
2 -0.121973 -0.629263 0.000000 0.282931
3 0.315271 -1.394874 -0.842817 2.737180
4 -1.372075 0.056470 -0.911675 1.555621
5 -0.828318 0.096984 0.994665 -0.396450
'''

不同列填充不同值

df.iloc[1,1] = NA
print(df)
'''
0 1 2 3
0 0.280375 1.887602 NaN 1.807013
1 -0.733194 NaN NaN 1.805263
2 1.654219 -0.088303 NaN -0.311653
3 -0.048358 0.534746 -0.036083 -1.462794
4 0.049589 -1.750243 0.021400 -1.317320
5 0.938722 0.606332 0.818729 -0.731558
'''

print(df.fillna({1:10, 2:20}))
'''
0 1 2 3
0 -0.596028 -0.993820 20.000000 -0.333727
1 0.458886 10.000000 20.000000 0.004068
2 0.453529 0.550983 20.000000 0.158485
3 -0.019701 -1.698847 -0.468249 -0.751607
4 0.348686 1.736886 2.158585 -1.790001
5 1.582322 -1.502224 -1.842465 -1.547464
'''

向上填充

print(df.fillna(method='ffill'))
'''
0 1 2 3
0 2.035090 0.515981 NaN -0.047395
1 0.272776 0.515981 NaN 1.440417
2 0.704964 0.097855 NaN -0.626416
3 -0.222160 -1.128264 1.393122 0.611554
4 -1.507354 -0.505203 -1.603342 0.133043
5 -0.093135 1.747892 0.623015 -0.748097
'''

向下填充

print(df.fillna(method='bfill'))
'''
0 1 2 3
0 -0.014719 0.986647 0.534659 0.266062
1 1.291622 0.726967 0.534659 -0.383410
2 -0.353421 0.726967 0.534659 -0.381417
3 1.114404 0.107649 0.534659 0.061185
4 -1.064036 0.663981 1.129786 0.882387
5 0.554677 0.910715 -0.707670 0.286981
'''

print(df.bfill())
'''
0 1 2 3
0 1.375592 -0.359649 -0.159129 -0.549489
1 -1.353560 0.030555 -0.159129 1.261686
2 -0.784974 0.030555 -0.159129 0.695045
3 -0.022894 -0.808983 -0.159129 -0.434400
4 -2.701784 -0.537891 -0.831746 0.890080
5 -1.888024 -1.223520 -0.902168 -1.069652
'''

异常值

源数据:

# DataFrame
data = pd.DataFrame(np.random.randn(1000,4 ))
print(data.describe())
'''
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean 0.002358 0.066633 0.035058 0.010281
std 0.976028 1.008953 1.037625 0.963921
min -3.245954 -3.677734 -3.356610 -3.137729
25% -0.658081 -0.630507 -0.629054 -0.667304
50% -0.002285 0.087801 0.096130 0.025558
75% 0.657763 0.725272 0.710590 0.678466
max 3.094999 3.373678 3.463601 3.077343
'''

# Series
data = pd.Series([1., -999., 2., -999., -1000, 3.])
print(data)
'''
0 1.0
1 -999.0
2 2.0
3 -999.0
4 -1000.0
5 3.0
dtype: float64
'''

把 -1000 替换成空值

replace_data = data.replace(-1000, NA)
print(replace_data)
''''
0 1.0
1 -999.0
2 2.0
3 -999.0
4 NaN
5 3.0
dtype: float64
'''

多个值替换

replace_data = data.replace([-999, 2], [np.nan, 0])         # -999 替换成 NaN ,2 替换成 0
print(replace_data)
'''
0 1.0
1 NaN
2 0.0
3 NaN
4 -1000.0
5 3.0
dtype: float64
'''

重复值

源数据:

data = pd.DataFrame({'k1': list('abababaa'), 'k2': [1,1,2,2,3,3,4,4]})
print(data)
'''
k1 k2
0 a 1
1 b 1
2 a 2
3 b 2
4 a 3
5 b 3
6 a 4
7 a 4
'''

drop_duplicates() 默认对行的重复数据进行删除

print(data.drop_duplicates())
'''
k1 k2
0 a 1
1 b 1
2 a 2
3 b 2
4 a 3
5 b 3
6 a 4
'''

drop_duplicates() ,函数内填入列名,则按指定的列进行去重

print(data.drop_duplicates(['k1']))
'''
k1 k2
0 a 1
1 b 1
'''

数据转换

源数据:

data = pd.DataFrame({'food': ['Banana', 'bone', 'Grass', 'fish', 'worm'],
'calorie': [10, 20, 30, 40, 50]})
print(data)
'''
food calorie
0 Banana 10
1 bone 20
2 Grass 30
3 fish 40
4 worm 50
'''

eat_animal = {
'grass': 'cattle',
'banana': 'monkey',
'fish': 'cat',
'worm': 'forg',
'bone': 'dog'
}

将数据进行转换,使其一一对应:

data['animal'] = data['food'].str.lower().map(eat_animal)         # map() 可接受有映射关系的字典对象
'''
0 monkey
1 dog
2 cattle
3 cat
4 forg
Name: food, dtype: object
'''

print(data)
'''
food calorie animal
0 Banana 10 monkey
1 bone 20 dog
2 Grass 30 cattle
3 fish 40 cat
4 worm 50 forg
'''

cut 分段:离散化

源数据:

ages = [20, 21, 23, 25, 28, 30, 37, 62, 69, 55, 35, 45, 57, 78, 83,96, 103]
bins_ = [18, 30, 60, 97, 110] # 边界值

使用 cut 离散化数据:

cut_ages = pd.cut(ages, bins=bins_)
print(cut_ages)
'''
[(18, 30], (18, 30], (18, 30], (18, 30], (18, 30], ..., (30, 60], (60, 97], (60, 97], (60, 97], (97, 110]]
Length: 17
Categories (4, interval[int64, right]): [(18, 30] < (30, 60] < (60, 97] < (97, 110]]
'''

统计各年龄段有多少个

print(cut_ages.value_counts())
'''
(18, 30] 6
(30, 60] 5
(60, 97] 5
(97, 110] 1
Name: count, dtype: int64
'''

加标签

group_name = ['少年', '青年','中年','老年']
cut_ages = pd.cut(ages, bins=bins_, labels=group_name)
print(cut_ages)
'''
['少年', '少年', '少年', '少年', '少年', ..., '青年', '中年', '中年', '中年', '老年']
Length: 17
Categories (4, object): ['少年' < '青年' < '中年' < '老年']
'''

print(cut_ages.value_counts())
'''
少年 6
青年 5
中年 5
老年 1
Name: count, dtype: int64
'''

data = np.random.randint(1,30, 30)
print(data)
'''
[ 9 21 7 3 5 12 19 27 18 28 15 22 25 27 3 27 14 15 15 5 8 11 25 10
28 24 13 12 19 29]
'''
print(pd.cut(data, 5))
'''
[(8.2, 13.4], (18.6, 23.8], (2.974, 8.2], (2.974, 8.2], (2.974, 8.2], ..., (23.8, 29.0], (8.2, 13.4], (8.2, 13.4], (18.6, 23.8], (23.8, 29.0]]
Length: 30
Categories (5, interval[float64, right]): [(2.974, 8.2] < (8.2, 13.4] < (13.4, 18.6] < (18.6, 23.8] <
(23.8, 29.0]]
'''

qcut()

data= np.random.randint(1, 100, 1000)
cut_data = pd.cut(data, 4)
print(cut_data)
'''
[(50.0, 74.5], (0.902, 25.5], (74.5, 99.0], (50.0, 74.5], (25.5, 50.0], ..., (50.0, 74.5], (25.5, 50.0], (0.902, 25.5], (74.5, 99.0], (25.5, 50.0]]
Length: 1000
Categories (4, interval[float64, right]): [(0.902, 25.5] < (25.5, 50.0] < (50.0, 74.5] <
(74.5, 99.0]]
'''
print(cut_data.value_counts())
'''
(0.902, 25.5] 237
(25.5, 50.0] 257
(50.0, 74.5] 237
(74.5, 99.0] 269
Name: count, dtype: int64
'''

案例: 爬虫数据清洗

# https://lishi.tianqi.com/beijing/202309.html

import requests
from lxml import etree

def get_html(month):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
'Accept-Encoding': 'gzip, deflate, br'
}

url = f'https://lishi.tianqi.com/beijing/{month}.html'
res = requests.get(url=url, headers=headers)
res_html = etree.HTML(res.text)
return res_html

month_list = pd.period_range('202101', '202310', freq='M').strftime("%Y%m") # freq 平率, ‘M’ 按月
df = pd.DataFrame(columns=['日期', '最高气温', '最低气温', '天气', '风向'])

lst = []


for i, month in enumerate(month_list):
res_html = get_html(month)

lis = res_html.xpath('//div[@class="tian_three"]/ul/li')

for li in lis:
item = {
'日期': li.xpath('./div[@class="th200"]/text()')[0],
'最高气温': li.xpath('./div[@class="th140"]/text()')[0],
'最低气温': li.xpath('./div[@class="th140"]/text()')[1],
'天气': li.xpath('./div[@class="th140"]/text()')[2],
'风向': li.xpath('./div[@class="th140"]/text()')[3],
}

lst.append(item)
df = pd.DataFrame(lst)

print(f'{month} 月份数据已获取')

df.to_excel('./weather.xlsx', index=False)

read_excel() 读取爬虫数据:

data = pd.read_excel('./weather.xlsx')
print(data)
'''
日期 最高气温 最低气温 天气 风向
0 2021-01-01 星期五 0℃ -11℃ 晴 东北风 2级
1 2021-01-02 星期六 0℃ -9℃ 晴 东北风 2级
2 2021-01-03 星期日 0℃ -7℃ 多云 东北风 2级
3 2021-01-04 星期一 -1-11℃ 晴 北风 3级
4 2021-01-05 星期二 -2-11℃ 晴 西北风 3级
... ... ... ... .. ...
1027 2023-10-25 星期三 27℃ 10℃ 晴 西北风 2级
1028 2023-10-26 星期四 0℃ -1℃ 阴 东风 4级
1029 2023-10-27 星期五 21℃ 10℃ 多云 西南风 1级
1030 2023-10-28 星期六 25℃ 18℃ 多云 南风 1级
1031 2023-10-29 星期日 36℃ 21℃ 多云 西风 2级

[1032 rows x 5 columns]
'''

数据拆分:日期列

print(data['日期'].str.split(' ', expand=True))      # expand =True 表示展开成2列
'''
0 1 2
0 2021-01-01 星期五
1 2021-01-02 星期六
2 2021-01-03 星期日
3 2021-01-04 星期一
4 2021-01-05 星期二
... ... ... ..
1027 2023-10-25 星期三
1028 2023-10-26 星期四
1029 2023-10-27 星期五
1030 2023-10-28 星期六
1031 2023-10-29 星期日

[1032 rows x 3 columns]
'''

data[['Date', 'Weak', '备注']] = data['日期'].str.split(' ', expand=True)
print(data)
'''
日期 最高气温 最低气温 天气 风向 Date Weak 备注
0 2021-01-01 星期五 0℃ -11℃ 晴 东北风 2级 2021-01-01 星期五
1 2021-01-02 星期六 0℃ -9℃ 晴 东北风 2级 2021-01-02 星期六
2 2021-01-03 星期日 0℃ -7℃ 多云 东北风 2级 2021-01-03 星期日
3 2021-01-04 星期一 -1℃ -11℃ 晴 北风 3级 2021-01-04 星期一
4 2021-01-05 星期二 -2℃ -11℃ 晴 西北风 3级 2021-01-05 星期二
... ... ... ... .. ... ... ... ..
1027 2023-10-25 星期三 27℃ 10℃ 晴 西北风 2级 2023-10-25 星期三
1028 2023-10-26 星期四 0℃ -1℃ 阴 东风 4级 2023-10-26 星期四
1029 2023-10-27 星期五 21℃ 10℃ 多云 西南风 1级 2023-10-27 星期五
1030 2023-10-28 星期六 25℃ 18℃ 多云 南风 1级 2023-10-28 星期六
1031 2023-10-29 星期日 36℃ 21℃ 多云 西风 2级 2023-10-29 星期日

[1032 rows x 8 columns]
'''

查看日期类型

print(data.info())
'''
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1032 entries, 0 to 1031
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 日期 1032 non-null object
1 最高气温 1032 non-null object
2 最低气温 1032 non-null object
3 天气 1032 non-null object
4 风向 1032 non-null object
5 Date 1032 non-null object
6 Weak 1032 non-null object
7 备注 1032 non-null object
dtypes: object(8)
memory usage: 64.6+ KB
None
'''

data['Date'] = pd.to_datetime(data['Date'])
print(data.info())
'''
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1032 entries, 0 to 1031
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 日期 1032 non-null object
1 最高气温 1032 non-null object
2 最低气温 1032 non-null object
3 天气 1032 non-null object
4 风向 1032 non-null object
5 Date 1032 non-null datetime64[ns]
6 Weak 1032 non-null object
7 备注 1032 non-null object
dtypes: datetime64[ns](1), object(7)
memory usage: 64.6+ KB
None
'''

数据拆分:风向列

data[['Wind', 'Leave']] = data['风向'].str.split(' ', expand=True)
print(data)
'''
日期 最高气温 最低气温 天气 风向 Date Weak 备注 Wind Leave
0 2021-01-01 星期五 0℃ -11℃ 晴 东北风 2级 2021-01-01 星期五 东北风 2级
1 2021-01-02 星期六 0℃ -9℃ 晴 东北风 2级 2021-01-02 星期六 东北风 2级
2 2021-01-03 星期日 0℃ -7℃ 多云 东北风 2级 2021-01-03 星期日 东北风 2级
3 2021-01-04 星期一 -1℃ -11℃ 晴 北风 3级 2021-01-04 星期一 北风 3级
4 2021-01-05 星期二 -2℃ -11℃ 晴 西北风 3级 2021-01-05 星期二 西北风 3级
... ... ... ... .. ... ... ... .. ... ...
1027 2023-10-25 星期三 27℃ 10℃ 晴 西北风 2级 2023-10-25 星期三 西北风 2级
1028 2023-10-26 星期四 0℃ -1℃ 阴 东风 4级 2023-10-26 星期四 东风 4级
1029 2023-10-27 星期五 21℃ 10℃ 多云 西南风 1级 2023-10-27 星期五 西南风 1级
1030 2023-10-28 星期六 25℃ 18℃ 多云 南风 1级 2023-10-28 星期六 南风 1级
1031 2023-10-29 星期日 36℃ 21℃ 多云 西风 2级 2023-10-29 星期日 西风 2级

[1032 rows x 10 columns]
'''

数据替换:删除最高气温中的单位(replace 是针对整个单元格去进行处理的,而 str.replace 是针对单元格内的单个元素进行处理)

print(data['最高气温'].str.replace('℃', ''))

data['最高气温']=data['最高气温'].str.replace('℃', '')
print(data)
'''
日期 最高气温 最低气温 天气 风向 Date Weak 备注 Wind Leave
0 2021-01-01 星期五 0 -11℃ 晴 东北风 2级 2021-01-01 星期五 东北风 2级
1 2021-01-02 星期六 0 -9℃ 晴 东北风 2级 2021-01-02 星期六 东北风 2级
2 2021-01-03 星期日 0 -7℃ 多云 东北风 2级 2021-01-03 星期日 东北风 2级
3 2021-01-04 星期一 -1 -11℃ 晴 北风 3级 2021-01-04 星期一 北风 3级
4 2021-01-05 星期二 -2 -11℃ 晴 西北风 3级 2021-01-05 星期二 西北风 3级
... ... ... ... .. ... ... ... .. ... ...
1027 2023-10-25 星期三 27 10℃ 晴 西北风 2级 2023-10-25 星期三 西北风 2级
1028 2023-10-26 星期四 0 -1℃ 阴 东风 4级 2023-10-26 星期四 东风 4级
1029 2023-10-27 星期五 21 10℃ 多云 西南风 1级 2023-10-27 星期五 西南风 1级
1030 2023-10-28 星期六 25 18℃ 多云 南风 1级 2023-10-28 星期六 南风 1级
1031 2023-10-29 星期日 36 21℃ 多云 西风 2级 2023-10-29 星期日 西风 2级

[1032 rows x 10 columns]
'''

data['最低气温']=data['最低气温'].str.replace('℃', '').astype('float') # astypt 设置数值类型
print(data)
'''
日期 最高气温 最低气温 天气 风向 Date Weak 备注 Wind Leave
0 2021-01-01 星期五 0 -11.0 晴 东北风 2级 2021-01-01 星期五 东北风 2级
1 2021-01-02 星期六 0 -9.0 晴 东北风 2级 2021-01-02 星期六 东北风 2级
2 2021-01-03 星期日 0 -7.0 多云 东北风 2级 2021-01-03 星期日 东北风 2级
3 2021-01-04 星期一 -1 -11.0 晴 北风 3级 2021-01-04 星期一 北风 3级
4 2021-01-05 星期二 -2 -11.0 晴 西北风 3级 2021-01-05 星期二 西北风 3级
... ... ... ... .. ... ... ... .. ... ...
1027 2023-10-25 星期三 27 10.0 晴 西北风 2级 2023-10-25 星期三 西北风 2级
1028 2023-10-26 星期四 0 -1.0 阴 东风 4级 2023-10-26 星期四 东风 4级
1029 2023-10-27 星期五 21 10.0 多云 西南风 1级 2023-10-27 星期五 西南风 1级
1030 2023-10-28 星期六 25 18.0 多云 南风 1级 2023-10-28 星期六 南风 1级
1031 2023-10-29 星期日 36 21.0 多云 西风 2级 2023-10-29 星期日 西风 2级

[1032 rows x 10 columns]
'''