Pandas_数据清洗与整理_全
阅读原文时间:2023年07月13日阅读:1
# 数据清洗与整(全)

# 1) 常见的数据清洗方法
# 2) 数据合并:多源数据的合并和连接
# 3) 数据重塑:针对层次化索引,学会 stack和 unstack
# 4) 字符串处理:学会 DataFrame中字符串函数的使用

# 一,常见的数据清洗方法

# 1, 查看数据基本信息
# df.info()
# df.describe()
# df.head(n): 显示数据前n行,不指定n,df.head()则会显示所有的行

# 2, 侦查缺失值
# df.isnull() :返回一个和 df结构相同的布尔型 DataFrame矩阵
# df.isnull().sum() :返回一个以 df各【列】值为nan个数为元素的序列
# df.isnull().sum().sum() :返回 df所有的 值为nan的个数和。

# df.notnull() :返回一个和 df结构相同的布尔型 DataFrame矩阵
# df.notnull().sum() :返回一个以 df各【列】值为非nan个数为元素的序列
# df.isnull().sum().sum() :返回 df所有的 值为非nan的个数和。

import numpy as np
import pandas as pd
from pandas import DataFrame,Series

df=DataFrame([[3,5,np.nan],[1,3,np.nan],['tom',np.nan,'ivan'],[np.nan,'a','b']])
df
df.isnull()
df.isnull().sum()
df.isnull().sum().sum()  # 4

df.notnull()
df.notnull().sum()
df.notnull().sum().sum()  # 8

# 3, 删除存在缺失值的整行或整列
# 不影响原 DataFrame数据
df.dropna(how='any',axis=0)   # 删除含有 NaN的整行数据     当 how='any'时,可以缺省
df.dropna(how='all',axis=0)   # 删除所有值为 NaN的整行数据
df.dropna(how='any',axis=1)   # 删除含有 NaN的整列数据     当 how='any'时,可以缺省
df.dropna(how='all',axis=1)   # 删除所有值为 NaN的整列数据 

# 4, 填充缺失值
# df.fillna?  查看函数帮助
# 当存在缺失值的行或列中的那些非缺失值很重要时,不能将整行或整列一删了之,这时需要填充缺失值:

df=DataFrame([[3,5,np.nan],[1,3,np.nan],['tom',np.nan,'ivan'],[np.nan,'a','b']])
df2=DataFrame([[0,1,2,np.nan],[4,5,6,np.nan],[np.nan,np.nan,np.nan,np.nan]])

# 对所有的 NaN 填充数据:
df3=df.fillna(0,inplace=False)  # 将 DataFrame中的所有 NaN填充为 0    inplace缺省时默认 inplace=False
df.fillna(0,inplace=True)       # 就地修改

# 对指定的某列或某几列填充数据 -- 1 使用索引和切片调用 fillna():
df[0].fillna(df[0].mean())         # 填充第 1列,使用第一列的统计非 NaA的平均值。
df[0:2]=df[0:2].fillna('enene')    # 填充第 1-3列,使用'enene'
df
df[[0,2]]=df[[0,2]].fillna('dada') # 填充第 1,3两列,使用'dada'  注意区别 df[[0,2]] 与 df[0,2]的不同,别用混了
df

# 对指定的某列或某几列填充数据 -- 2 fillna()的参数传入字典:
df.fillna({0:df[0].mean()})     # 填充第 1列,使用第一列的统计非 NaA的平均值。
df2.fillna({0:0,2:'aa'})        # 给指定的列填充  第一列的填充值为 0,第 3列的填充值为 'aa'
df.fillna(method='ffifll')      # df.reindex()里也有这个参数

# 对指定的某行或某几行填充数据 -- 1 使用索引和切片调用 fillna():
df.loc[0].fillna(df.loc[0].mean(),inplace=True)         # 填充第 1行,使用第一行的统计非 NaA的平均值。
df.loc[0:2]=df.loc[0:2].fillna('enene')                 # 填充第 1-3行,使用'enene'
df
df.loc[[0,2]]=df.loc[[0,2]].fillna('dada')              # 填充第 1,3两行,使用'dada'
df

# 对指定的某行或某几行填充数据 -- 2 fillna()的参数传入字典:(不推荐)
# 给行填充不能使用字典参数加 axis=1的方式,会报错的,但可以使用转置的方法转成列再填充,然后转置回来。
df=df.T.fillna({2:'aa'}).T
df=df.T.fillna({0:0,3:'aa'}).T  # 给指定的行填充  转置两次

# 5, 移除重复数据
# 移除行数据完全相同的行:
df.duplicates()                   # 返回一个布尔型序列  True表示与前面行数据有重复
df.drop_duplicates()              # 保留第一次出现的行数据,删除其他的重复行数据  keep='first' 可缺省
df.drop_duplicates(keep='last')   # 保留最后一次出现的行数据,删除其他的重复行数据

# 移除以指定列所谓判断重复标准的的行:
df.drop_duplicates(subset=['sex','year'],keep='last')   # 以指定的'sex','year'列作为判断依据。

# 6, 替换值:df.replace()
df.replace('','未知')                # 将 df中所有的 ""替换为 "未知"
df.replace(['',2001],['未知',2002])  # 将 df中所有的 ""替换为 "未知",将 2001替换为 2002
df.replace({'':'未知',2001:2002})    # 将 df中所有的 ""替换为 "未知",将 2001替换为 2002

# 7, 利用函数或映射进行数据转换
# 判断分数等级:90-100 优秀; 70-89 良好; 60-69 及格; <60 不及格
# python可以使用 循环加 if 判断来解决
# pandas 可以使用 自定义函数+map()函数来实现

data={
    'name':['张三','李四','王五','马六'],
    'grade':[75,52,63,99]
}

df=DataFrame(data)

def f(x):
    if x>=90:
        return '优秀'
    elif 70<=x<90:
        return '良好'
    elif 60<=x<70:
        return '合格'
    else:
        return '不合格'

df['class']=df.grade.map(f)    # df['class']=df['grade'].map(f)
df

# 8, 检测异常值
df=DataFrame(np.arange(10),columns=['X'])
df['Y']=df['X']*2+0.5
df.iloc[9,1]=185 # 将第 9行第 1列的值由 18.5改为 185
df.plot(kind='scatter',x='X',y='Y')

# 9, 虚拟变量(暂时用不到,数学建模机器学习)
# 对于单类别的数据:使用 pd.get_dummies()
df = DataFrame(
    {
        '朝向':['东','南','东','西','北']
        '价格':[1200,2000,1200,1100,800]
    }
)
df
pd.get_dummies(df['朝向'])

# 对于多类别的数据:使用 apply()
df = DataFrame(
    {
        '朝向':['东/北','南/西','东','西/北','北']
        '价格':[1200,2000,1200,1100,800]
    }
)
df
dummies=df['朝向'].apply(lambda x:Series(x.split('/')).value_counts())
dummies

# 二,数据合并与重塑
# 当要数据来源于多处时,就需要对数据进行合并和重塑。

# 1, pd.merge(df1,df2,on='fruit',how='inner'...)函数:

# how--how='inner' 内连接,交集(默认)
# how='outer' 外连接,并集
# how='left'左连接     以 df1为主表,右表没数据为 NaN
# how='right'右连接    以 df2为主表,右表没数据为 NaN

# left--指参数列表里第一个 Daframe
# right--指参数列表里第二个 Daframe
# on--用于连接的列名
# left_on--df1用于连接的列名  与 right_on--df2用于连接的列名  成对出现
# left_index--df1的行索引作为连接键    因为行索引也可以看作是一列
# right_index--df2的行索引作为连接键
# sort--合并后对数据进行排序,默认为True
# suffixes--修改重复名

# 通过一个键或多个键(DataFrame的列)将两个DataFrame按行合并起来,类似于SQL。
price=DataFrame(
    {
        'fruit':['apple','banana','orange'],
        'price':[23,32,45]
    }
)

amount=DataFrame(
    {
        'fruit':['apple','banana','apple','apple','banana','pear'],
        'amount':[5,3,6,3,5,7]
    }
)

pd.merge(amount,price,on='fruit')               # 交集
pd.merge(amount,price,on='fruit',how='outer')   # 并集
pd.merge(amount,price,on='fruit',how='left')    # 左连接

# 多对多的连接,笛卡尔积:
# 只使用一个键来连接时,price2里有2个 apple,amount2里有3个apple,连接的结果就有6个apple.

price2=DataFrame(
    {
        'fruit':['apple','banana','orange','apple'],
        'price':[23,32,45,25]
    }
)

amount2=DataFrame(
    {
        'fruit':['apple','banana','apple','apple','banana','pear'],
        'amount':[5,3,6,3,5,7]
    }
)

pd.merge(amount2,price2,on='fruit',how='outer')   # 并集

# 多个键合并,使用列表:
df1=DataFrame(
    {
        'k1':['one','one','two'],
        'k2':['a','b','a'],
        'v1':[2,3,4]
    }
)

df2=DataFrame(
    {
        'k1':['one','one','two','two'],
        'k2':['a','a','a','b'],
        'v1':[5,6,7,8]
    }
)

# pd.merge(df1,df2,on='k1',how='outer')           # 当只指定一个连接键的时候,容易出现笛卡尔积
pd.merge(df1,df2,on=['k1','k2'],how='outer')      # 用两个连接键作为约束,就可以避免不必要的笛卡尔积

# suffixes 处理重复列名的问题:
pd.merge(df1,df2,on='k1',how='outer',suffixes=('_left','_right'))   # suffixes 处理重复列名的问题  

# 使用行索引作为连接键来使用:right_index=True  或 left_index=True
df1=DataFrame(
    {
        'k':['a','b','a'],
        'v1':[2,3,4]
    }
)

df2=DataFrame({'v2':[5,6,7,8]}index=['a','b'])
pd.merge(df1,df2,left_on='key',right_index=True)

# 将两个Dataframe同时按照索引连接起来:
# 1)方法一,使用 merge:
pd.merge(df1,df2,left_on='k',left_index=True,right_index=True,how='outer')
# 2)方法二,使用 join:
df1.join(df2,how='outer')

# 2, pd.concat([s1,s2],axis=0,join='outer',join_axes=[['b','a']])  

# 默认 axis=0   默认 join='outer'   join只有 inner和 outer两个值可选.  join_axes用来指定索引顺序
# 当要合并的两个 DataFrame没有连接键时,不能使用 merge,可以使用 contact。
# concat只会连接不会去重。想要对结果去重,得使用 df.drop_duplicates()。

# 对于 Series:
s1=Series([0,1],index=['a','b'])
s2=Series([0,3],index=['a','d'])  # a标签与 s1重复
s3=Series([4,5],index=['e','f'])

pd.concat([s1,s2,s3])         # 共 6行 1列    axis=0
pd.concat([s1,s2,s3],axis=1)  # 共 5行 3列    axis=1
pd.concat([s1,s2],axis=1,join='outer')     # join='outer' 并集    2列 3行  a b d
pd.concat([s1,s2],axis=0,join='outer')     # join='outer' 并集    1列 4行  a b a d  默认
pd.concat([s1,s2],axis=1,join='inner')     # join='inner' 交集    2列 1行  a
pd.concat([s1,s2],axis=0,join='inner')     # join='inner' 交集    1列 4行  a并集    默认

# 如果想在结果中很容易区分出参与连接的对象,可以使用 keys参数给连接对象创建一个层次化索引。
pd.concat([s1,s2],axis=0,join='inner',keys=['first','second'])
pd.concat({'first':s1,'second':s2},axis=0,join='inner') 

pd.concat([s1,s2],axis=1,join='inner',keys=['first','second'])
pd.concat({'first':s1,'second':s2},axis=1,join='inner') 

# 对于 DataFrame:
price=DataFrame(
    {
        'fruit':['apple','banana','orange'],
        'price':[23,32,45]
    }
)

amount=DataFrame(
    {
        'fruit':['apple','banana','apple','apple','banana','pear'],
        'amount':[5,3,6,3,5,7]
    }
)

pd.concat([price,amount],axis=1)

# 重新生成默认索引:对于 axis=0时有用
price=DataFrame(
    {
        'fruit':['apple','banana','orange'],
        'price':[23,32,45]
    },index=['a','b','c']
)

amount=DataFrame(
    {
        'fruit':['apple','banana','apple','apple','banana','pear'],
        'amount':[5,3,6,3,5,7]
    },index=['a','b','c','d','e','f']
)

pd.concat([price,amount],axis=0)
pd.concat([price,amount],axis=0,ignore_index=True)  # 不会改变数据结构,只是生成新的默认索引

# 3, combine_first()合并
# combine_first()使用的连接键是行索引(行索引可以看作为一个列)
# 如果需要合并的两个 DataFrame存在重复的索引,在这种情况下,若使用 merge和 concat方法都不能准确的解决问题,此时需要
# combine_first方法,类似于打补丁,以调用的 DataFrame为主,参数里的 DataFrame作为补丁。
price=DataFrame(
    {
        'fruit':['apple','banana',np.nan,'pear'],
        'price':[23,32,np.nan,np.nan]
    },index=['a','b','c','d']
)

amount=DataFrame(
    {
        'fruit':['apple','banana','apple','apple','banana','pear'],
        'price':[5,3,6,3,5,7],
        'amount':[1,1,1,1,1,1]
    },index=['a','b','c','d','e','f']
)

price.combine_first(amount)

# 4, 数据重塑     stack 堆叠
# 1) df.stack()方法: 将每个行索引对应的列索引堆叠起来形成内层行索引变成层次化索引的序列 Series
# 2) df.unstack()方法: df.stack()的反操作。

df=DataFrame(np.arange(9).reshape(3,3),index=['a','b','c'],columns=['one','two','three'])
df.index.name='alph'
df.columns.name='number'

Se=df.stack()  # 其结果是一个层次化的序列
type(Se) # pandas.core.series.Series
Se.unstack(),Se.unstack(1),Se.unstack('number')
# unstack() 可以指定参数,默认是 Se的内层索引的名字(即原 DataFrame df1的columns.name)或表示层次结构的数字 1
# 如果将参数指定为 0或 Se的外层索引的名字(即原 DataFrame df1的index.name),则得到 df1的转置。
Se.unstack(0),Se.unstack('alph')   

# 对于本身就是层次化索引的 DataFrame,其 stack()和 unstack()用到再说吧。

# 三, 字符串处理

# 1) Python 内建字符串方法:
# str.count()               返回字符串在字符串中的非重叠出现的次数
# str.endwith()             如果字符串以后缀结尾则返回 True
# str.startwith()           如果字符串以后缀结尾则返回 True
# str.join()                使用字符串作为间隔,用于黏合其他字符串的序列
# str.index()               如果在字符串中找到,则返回字符串的第一个字符的位置,如果找不到,则引发 ValueError
# str.find()                类似于 str.index(),只是找不到时返回 -1.
# str.rfind()               返回目标字符串在字符串中最后出现的位置,找不到则返回 -1.
# str.replace()             使用一个字符串替代另一个字符串
# str.strip(),str.rstrip().str.lstrip()  修剪空白,包括换行符;
# str.split()               使用分隔符将字符串拆分为子字符串的列表
# str.lower()               将大写字母转换为小写字母
# str.upper()               将小写字母转换为大写字母
# str.casefold()            将字符串转换为小写,并将任何特定于区域的变量字符串组合转换为常见的可比较的形式
# str.ljust(),str.rjust()   左对齐或右对齐;用空格(或其他一些字符)填充字符串的相反侧以返回具有最小宽度的字符串。

# 2) pandas 处理字符串数据的矢量化函数:
# https://blog.csdn.net/dta0502/article/details/81839048
# 向量化字符串方法:
# se.str.cat()            根据可选的分隔符,按元素黏合字符串
# se.str.contains()       返回是否含有某个模式/正则表达式的布尔值数组
# se.str.count()          模式出现次数的计算
# se.str.extract()        使用正则表达式从字符串 Series中分组抽取一个或多个字符串;返回的结果是每个分组形成一列的 DataFrame
# se.str.endswith()       等价于每个元素使用 x.endwith(模式)
# se.str.startswith()     等价于每个元素使用 x.startswith(模式)
# se.str.match()          使用 re.match()将正则表达式应用到每个元素上,将匹配分组以列表的形式返回。
# se.str.findall()        找出字符串中所有的模式/正则表达式匹配项,以列表返回。
# se.str.get()            对每个元素进行索引 (获取第 i 个元素)
# se.str.isalnum()        等价于内建的 str.alnum()
# se.str.isalhpa()        等价于内建的 str.isalhpa()
# se.str.isdecimal()      等价于内建的 str.isdecimal()
# se.str.isdigt()         等价于内建的 str.isdigt()
# se.str.islower()        等价于内建的 str.islower()
# se.str.isupper()        等价于内建的 str.isupper()
# se.str.isnumeric()      等价于内建的 str.isnumeric()
# se.str.join()           根据传递的分隔符,将 Series中的字符串联合
# se.str.len()            计算每个字符串的长度
# se.str.lower/se.str.upper()    对每个元素进行 大小写转换
# se.str.repeat()         重复值(如, se.str.repeat(3))
# se.str.replace()        以其他字符串替代模式/正则表达式的匹配项
# se.str.slice()          对 series中的字符串进行切片
# se.str.split()          以分隔符或正则表达式对字符串进行拆分
# se.str.strip()          对字符串的两侧的空白进行消除,包括换行符。
# se.str.rtrip()          对字符串的右侧的空白进行消除。
# se.str.ltrip()          对字符串的左侧的空白进行消除。
# se.str.pad()            将空白加到字符串的 左边,右边,或者两边
# se.str.center()         等价于 pad(side='both')  

# 1, 字符串方法:以 str.split() 为例:
# 示例,将一列字符串数据分成两列:
data={
    'data':['张三|男','李四|女','王五|女','马六|男']
}

df=DataFrame(data)

# 传统方法 1:
df['name']=df['data'].apply(lambda x:x.split("|")[0])
df['gender']=df['data'].apply(lambda x:x.split("|")[1])
del df['data']
# df=df[['name','gender']]  # 重新排列列的顺序
df

# 传统方法 2:
df=df['data'].apply(lambda x:Series(x.split("|")))  #
df.columns=['name','gender']  # 发现结果里没有了列名,重新按位置定义列名
# df.rename(columns={'data':'name'}, inplace = True)  # 重命名列
df

# pandas 方法:
# pandas中字段的 str属性可以轻松调用字符串的方法,并矢量化到整个字段数据中:
# df['data'].str.split("|")  # dataframe 的 data字段的 str属性 调用了 split()方法   返回一个以列表为元素的序列

data={
    'data':['张三|男','李四|女','王五|女','马六|男']
}

df=DataFrame(data)
se=df['data'].str.split("|")   # type(se)  # pandas.core.series.Series
df['name']=se.str[0]
df['gender']=se.str[1]
del df['data']
df

# 2,正则表达式:

# re模块中 常用的正则表达式方法:(注意不是 pandas可用的)
# 1)findall   将字符串中的所有非重叠匹配模式以列表形式返回。
# 2)finditer  与 findall 类似,只是返回的是 迭代器
# 3)match     在字符串起始位置匹配模式,也可以将模式组件匹配到分组中;如果模式匹配上了,返回match_object对象,否则返回 None
# 4) search,split,sub,subn等详见 正则表达式那个总结。

# 字符串的矢量化操作同样适用于正则表达式:
df=DataFrame({
    'email':['100@qq.com','111@qq.com','222@qq.com']
})

se=df['email'].str.findall('(.*?)@')
type(se)  # pandas.core.series.Series
df['qq']=df['email'].str.findall('(.*?)@').str.get(0)
df


import pandas as pd
from pandas import DataFrame
df=DataFrame({
    'email':['100@qq.com','111@qq.com','222@qq.com']
})

# se=df['email'].str.findall('(.*?)@')
# type(se)  # pandas.core.series.Series
# df['qq']=se.str.get(0)  # get()是对每一个元素进行索引。
# df
df['qq']=df['email'].str.findall('(.*?)@').str.get(0)   # 注,没有 str.search()方法。
df

.dataframe tbody tr th:only-of-type { vertical-align: middle }
{ vertical-align: top }
.dataframe thead th { text-align: right }

email

qq

0

100@qq.com

100

1

111@qq.com

111

2

222@qq.com

222