pandas数据清洗实战基础

pandas数据清洗实战

本节目标:对于字符串的清洗,空值的处理,重复值的处理.

本节技术点:dropna(),strip(),replace,extract,isna

本节阅读需要(20)min。
本节实操需要(20)min。



前言

数据清洗的目的是为了得到我们的目标值.也就是成为我们需要的样子。
所以这个概念很宽泛。

理论上来说只要学会行列操作不就好了吗?

😄不对的,因为我们上游的数据往往是残缺的,格式奇怪的,有重复的。
所以我们需要针对一些常见的过滤情况做一下学习。

因为我们需要处理的数据总体分为两大类,string类型和数值类型。
所以清洗也就分为了两大块。


一、字符串类型

先熟悉一下字符串的常见操作。因为我们处理数据是以一个数据列为单元。
所以我们用series来举例。字符串一般也是按列处理的。因为一般都有具体含义

类型约束

首先我们要确保类型是字符串,尤其是数值格式的字符串。
我们可以在初始化读入的时候通过dtype

s = pd.Series(["a", 2, np.nan], dtype="string")

或者之后astype约束类型为字符串类型

s.astype("string")

注意:

np.nan不会被转化为string,相当于跳过这个。

s.dropna() 可以去除np.nan。一般都会在处理之前先过滤空值!!!

str方法容器

我个人认为str是pandas处理字符串的容器,相当于抽象数据类型。

s = pd.Series(["a", None, "b"], dtype="string")
s.str.count("a") # 计数a这个字符串出现的次数。
s.str.len()

首先都需要str之后再调用各种pd重载的字符串方法。

s.str.isdigit() # 判断是否是小数
s.str.match("a")
s.str.contains("a", na=False)
# 进一步筛选
s[s.str.contains("a", na=False)]

返回的都是boolean类型的,所以根据前面所学可以作为清洗的输入参数
比如s[s.str.contains(“a”, na=False)]那么只剩下a一个了。

字符串的连接

我们有的时候需要个性化的修改某一列的字符串内容。

# 没有参数时自身像列表一样链接
s = pd.Series(["a", "b", "c", "d"], dtype="string")
s.str.cat(sep=",") # 'a,b,c,d'

# 有参数时,像广播一样对位相加。
t = pd.Series(["a", "b", np.nan, "d"], dtype="string")
s.str.cat(t)
0      aa
1      bb
2    <NA> # 注意nan
3      dd
dtype: string
d = pd.concat([t, s], axis=1)
s.str.cat(d, na_rep="-") # 针对na进行了替换

此外,cat还有其他参数甚至接近于merge,但是我不建议用cat处理过于复杂的数据。
一般都是通过已有的两个字符串列合并为一个新的列.

常见的清洗

我根据我的经验按照常见的清洗顺序整理如下:

  1. 读入之后一般是去除空值比较多。所以一般s.dropna()。
  2. 字符串原始文件中常常还有多余的空格,s.str.strip()去除两边的多余空格。
  3. 格式一致化如果是纯字母的往往需要大小写一致,s.str.lower()等来大小写一致。或者使用replace(" ", “_”)来替换
  4. 这一步一般是过滤,无非两种,通过匹配过滤;或者通过统计过滤比如len()长度。
# 去除两边的多余字符
s.str.strip()
s.str.lstrip()
s.str.rstrip()
s.str.removeprefix("str_")
s.str.removesuffix("_str")

replace大魔王!!!

dollars = pd.Series(["12", "-$10", "$10,000"], dtype="string")
dollars.str.replace(r"-\$", "-", regex=True)
dollars.str.replace("-$", "-", regex=False)

replace对于清洗中的格式一致化十分重要功能也很强大。
上面的效果一致。我建议在复杂的时候用regex,简单的时候关闭直接替换就行。
看一个比较复杂的replace的实例

pat = r"[a-z]+"
def repl(m):
    return m.group(0)[::-1]
pd.Series(["foo 123", "bar baz", np.nan], dtype="string").str.replace(
    pat, repl, regex=True
)

group(0)的意思是re.match之后的捕获分组的第一个。[::-1]字符串反转

0    oof 123
1    rab zab
2       <NA>
dtype: string

所以结果如此也不难理解。
总体来看要想replace用的好需要很要的regex功底。
基本都是如下格式:

s[s.str.contains(“a”, na=False)]

字符串的过滤一般是匹配为主

提取和拆分(高级)

有的时候比如说我们面对这样的字符串。比如说“数学一般语文优秀”。
我们需要拆分为两列,一列描述语文,一列描述数学。

用到str.extract,返回的是一个df对象!!!类似于Excel分列

s = pd.Series(["语文优秀数学一般", "语文一般数学优秀", "语文优秀数学优秀"], index=["A", "B", "C"], dtype="string")
two_groups = u"语文(?P<语文>.*)数学(?P<数学>.*)" 
pd1 = s.str.extract(two_groups, expand=True)
# pd1.columns = ["语文","数学"] # 可省略
pd1

?P<语文>通过分组匹配添加标签,我们可以省略如上的列名。
P一定要大写

二、数值类型

数值类型相对于字符串还简单点.
基本就是通过基础运算和比较运算.

类型约束

数值信息的每一列都需要严格的约束数据类型.比如int8,int16等.

pd.array([1, 2, np.nan, None, pd.NA], dtype="Int64")
s.astype("Int64")

正常的数会强制转换为dtype的类型.np.nan, None, pd.NA都会转化为pd.NA类型.
pd.NA一般是不可以参加计算的!!!
或者也可以认为pd.NA参与的运算结果基本还是pd.NA.
groupby等都是直接忽略掉。

# 抽样
s = pd.Series([0, 1, 2, 3, 4, 5])
s.sample()
s.sample(n=3)
s.sample(frac=0.5)

一般抽样之后会评估一下数据如何处理,是作为预处理步骤的。

NA的处理

个人经验来看,因为大部分情况下数据都是有意义的。
所以要么用对应的空值代替,要么用对应的平均值等代替。
这个要看具体的业务内容了。

pandas官网写了一大通。
**但是第一剔除,第二填充。**相信我这是真理

粗暴的删除

# 删除有两种
df.dropna(axis=0) # 按行删除
df.dropna(axis=1) # 按列删除

一般按行多一点,删除无效的数据条目。
有选择的删除

df = pd.DataFrame(
    np.random.randn(5, 3),
    index=["a", "c", "e", "f", "h"],
    columns=["one", "two", "three"],
)
df2 = df.reindex(["a", "b", "c", "d", "e", "f", "g", "h"]) # 制造NA
df2.isna()
s1 = df2["one"]
s1[~s1.isna()] # 等价于s1[s1.notna()]
s2  = s1[s1.notna()]
s2 # series会自动删除NA

series会自动删除NA,但是df不会。但是够了,我们可以用列进一步删选出行。

df2.loc[s2.index]

在这里插入图片描述
我们两次用到了筛选器,第一次是series通过【boolean列表】筛选,
第二次是df对象通过loc【index】的方式筛选的。

填充

df2.fillna(0)
df2["one"].fillna("missing")

三、其他的操作

dates = pd.date_range('1/1/2000', periods=8)
df = pd.DataFrame(np.random.randn(8, 4),
                  index=dates, columns=['A', 'B', 'C', 'D'])

# 添加自然数index
dfa.A = list(range(len(dfa.index)))  # A需要提前存在
dfa['A'] = list(range(len(dfa.index))) # 如果没有就初始化,有就替换

# 交换两列的位置.
df[['B', 'A']] = df[['A', 'B']] # 等价于df.loc[:, ['B', 'A']] = df[['A', 'B']]
# 重新设置index
df.index = df['A'] # 不等价于
df.reindex(df['A']) # 上下截然不同,会有很多NA,因为没有意义

df.loc[:, [‘B’, ‘A’]]还可以这么写df.loc[:, lambda df: [‘A’, ‘B’]]
dfd.columns.get_loc(‘A’)
dfd.columns.get_indexer([‘A’, ‘B’])
我只想说,秀!!!但是我觉得没有记忆的必要。。。

其实意思是一样的都是传入位置信息的列表。

s.reindex([1, 2, 3])虽然设置index但是是相对于原来的主键扩充或减少,是会维持原来主键的意义以及关系的。

总结

tips:
大部分的聚类算法都是忽略NA值的。
reindex之后很容易产生大片的NA。

一个重要的方法论。

很多时候,我们的数据的量是很大的。而我们只是为了获得某一个宏观的结果!!!

所以我们的过滤必须要和我们的业务保持密切的联系

不应该在过滤上花费过多的时间和精力,少部分杂项也是不会影响整体结论的。

吾生也有涯而知也无涯以有涯随无涯殆已
--庄子

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

演技拉满的白马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值