美文网首页
数据清洗和准备

数据清洗和准备

作者: 辉辉_teresa | 来源:发表于2025-04-25 21:27 被阅读0次

数据清洗和准备

处理缺失数据

# 对于float64数据类型的数据,pandas使用浮点值NaN(Not a Number)表示缺失数据
float_data = pd.Series([1.2,-3.5,np.nan,0])
# 0    1.2
# 1   -3.5
# 2    NaN
# 3    0.0
# dtype: float64

# 使用isna方法会生成一个布尔型Series
float_data.isna()
# 0    False
# 1    False
# 2     True
# 3    False
# dtype: bool

# Python内置的None值也可以作为NA
string_data = pd.Series(["aardvark",np.nan,None,"avocado"])
float_data = pd.Series([1,2,None],dtype='float64')

NA处理方法:

  • dropna:根据各标签的值中是否存在缺失数据,对轴标签进行过滤,可通过阀值调节对缺失值的容忍度
  • fillna:用指定值或插值方法(如ffill或bfill)填充缺失数据
  • isnull:返回布尔值,表示哪些值是缺失值/NA
  • notnull:isna的否定式,对于非NA值返回True,对于NA值返回False

过滤缺失数据

# dropna返回一个仅含非空数据和索引值的Series
data = pd.Series([1,np.nan,3.5,np.nan,7])
data1 = data.dropna()  # 等价与 data[data.notna()]
# 0    1.0
# 2    3.5
# 4    7.0

# dropna默认丢弃任何含有缺失值的行。  这些函数默认返回的是新对象,并不修改原始对象的内容
data = pd.DataFrame([[1.,6.5,3.],[1.,np.nan,np.nan],
                     [np.nan,np.nan,np.nan],[np.nan,6.5,3.]])
data.dropna()
# 传入how="all"将只丢弃全为NA的那些行
data.dropna(how="all")
# 丢弃列
data.dropna(axis="columns",how="all")
# 下含有一定缺失值数量的行
data.dropna(thresh=2) # 出现2个及2个以上的nan时,该行删除

填充缺失数据

# fillna,将缺失值替换为一个常数值
df.fillna(0)
# 调用fillna时使用字典,就可以实现对不同的列填充不同的值
df.fillna({1:0.5,2:0})

# 用于重建索引的相同的插值方法也可以用于fillna
df = pd.DataFrame(np.random.standard_normal((6,3)))
df.iloc[2:,1]=np.nan
df.iloc[4:,2]=np.nan
df.fillna(method="ffill")  # 插入前面的值
df.fillna(method="ffill",limit=2) # 填充两个

fillna函数参数:

  • value:用于填充缺失值的标量值或字典型对象
  • method:插值方式:"bfill"(后向填充)或“ffill"(前向填充),默认为None
  • axis:待填充的轴(“index”或"columns"),默认为axis="index"
  • limit:对于前向填充和后向填充,可以连续填充的最大范围

数据转换

删除重复数据

data = pd.DataFrame({"k1":["one","two"]*3+["two"],
                     "k2":[1,1,2,3,3,4,4]})
# duplicated方法返回一个布尔型Series,表示各行是不是重复行(列上的值在前面的行中出现过)
data.duplicated()
# drop_duplicates方法,它会返回一个DataFrame,其中只保留了duplicated数组中为False的行
data.drop_duplicates()
# 只根据“k1”列过滤重复项
data.drop_duplicates(subset=["k1"])
# duplicated和drop_duplicates默认保留的是第一个出现的值组合。传入keep="last"则保留最后一个
data.drop_duplicates(["k1","k2"],keep="last")

利用函数或映射进行数据转换

data = pd.DataFrame(
    {
        "food":["bacon","pulled pork","bacon"],
        "ounces":[4,3,12]
    }
)
# 假设你想添加一列,表示该肉类的动物种类。我们先编写一个不同肉类到动物的映射
meat_to_animal={
    "barcon":"pig",
    "pulled pork":"cow"
}
# Series的map方法可以接收一个函数或含有映射关系的字典型对象,来实现值转换
data["animal"] = data["food"].map(meat_to_animal)

# 函数
def get_animal(x):
    return meat_to_animal[x]
data["food"].map(get_animal)

替换值

data = pd.Series([1.,-999.,2.,-999.,-1000.,3.])
# replace替换值
data.replace(-999,np.nan)
# 替换多个值
data.replace([-999,-1000],np.nan)
# 每个值替换不同的值
data.replace([-999,-1000],[np.nan,0])
# 传入的参数也可以是字典
data.replace({-999:np.nan,-1000:0})

重命名轴索引

与Series中的值一样,轴标签也可以通过函数或映射进行转换,从而得到一个新的不同标签的对象。还可以就地修改轴,无须新建一个数据结构。

如果使用rename,则无须手动复制DataFrame并给index和columns属性赋新值


data = pd.DataFrame(np.arange(12).reshape((3,4)),
                    index=["Ohio","Colorado","New York"],
                    columns=["one","two","three","four"])

# 轴索引也有map方法
def transform(x):
    return x[:4].upper()
data.index.map(transform)
# 可以将其赋值给index的属性,对DataFrame进行就地修改
data.index = data.index.map(transform)
# 如果要创建数据集转换后的版本,并且不修改原始数据,比较实用的方法是rename
data.rename(index=str.title,columns=str.upper)
# rename可以结合字典型对象实现对部分轴标签的更新
data.rename(index={"OHIO":"INDIANA"},columns={"three":"peekaboo"})

离散化和分箱

为了便于分析,连续数据常常被离散化或拆分为“箱子"。

ages = [20,22,25,27,21,23,37,31,61,45,41,32]
bins = [18,25,35,60,100]
age_categories = pd.cut(ages,bins) # 将这些数据划分为“18到25”“26到35”“35到60”以及“60以上”几个组
# [(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]

age_categories.codes # [0 0 0 1 0 0 2 1 3 2 2 1]
age_categories.categories # IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]], dtype='interval[int64, right]') 
age_categories.categories[0] # (18, 25]
# 分箱计数
pd.value_counts(age_categories)
# (18, 25]     5
# (25, 35]     3
# (35, 60]     3
# (60, 100]    1
# Name: count, dtype: int64

# 哪边是封闭的可以通过right=False进行修改
pd.cut(ages,bins,right=False)

# 通过传递一个列表或数组到labels选项,设置自己的分箱名称
group_names = ["Youth","YoungAudit","MiddleAged","Senior"]
pd.cut(ages,bins,labels=group_names)
# ['Youth', 'Youth', 'Youth', 'YoungAudit', 'Youth', ..., 'YoungAudit', 'Senior', 'MiddleAged', 'MiddleAged', 'YoungAudit']
# Length: 12
# Categories (4, object): ['Youth' < 'YoungAudit' < 'MiddleAged' < 'Senior']

# 如果向pandas.cut传入的不是确切的分箱边界,而是分箱的数量,则会根据数据的最小值和最大值计算得到等长的箱
data = np.random.uniform(size=20)
pd.cut(data,4,precision=2) # precision=2限定小数点后只有两位
#[(0.51, 0.75], (0.51, 0.75], (0.51, 0.75], (0.27, 0.51], (0.028, 0.27], ..., (0.51, 0.75], (0.028, 0.27], (0.75, 1.0], (0.27, 0.51], (0.51, 0.75]]
#Length: 20
#Categories (4, interval[float64, right]): [(0.028, 0.27] < (0.27, 0.51] < (0.51, 0.75] < (0.75, 1.0]]

pandas.qcut函数类似于pandas.cut,它可以根据样本分位数对数据进行划分。根据数据的分布情况,pandas.cut可能无法使各个分箱中含有相同数量的数据点。而由于pandas.qcut使用的是样本分位数,因此可以得到大小基本相等的分箱:

data = np.random.standard_normal(1000)
quartiles = pd.qcut(data,4,precision=2)

# pandas.cut类似,你也可以传递自定义的分位数(0到1之间的数值,包含端点)
pd.qcut(data,[0,0.1,0.5,0.9,1.]).value_counts()

检测和过滤异常值

data = pd.DataFrame(np.random.standard_normal((1000,4)))
# 找出某列中绝对值大小超过3的值
col = data[2]
col[col.abs()>3]
# 选出全部含有“超过3或-3的值”的行,你可以在布尔型DataFrame中使用any方法
data[(data.abs()>3).any(axis="columns")]
# 下面的代码可以将绝对值超过3的值赋值为-3或3,将所有值限制在区间-3~3之内
data[data.abs()>3] = np.sign(data)*3  # data的值是正还是负,np.sign(data)可以生成1和-1

置换和随机采样

利用numpy.random.permutation函数可以轻松实现对Series或DataFrame列的置换(即随机重排序)。通过对需要排列的轴的长度调用permutation,可产生一个表示新顺序的整数数组:

df = pd.DataFrame(np.arange(5*7).reshape((5,7)))
sampler = np.random.permutation(5) # [2 1 4 3 0]
df.take(sampler)  # 等价 df.iloc[sampler]
#     0   1   2   3   4   5   6
# 2  14  15  16  17  18  19  20
# 1   7   8   9  10  11  12  13
# 4  28  29  30  31  32  33  34
# 3  21  22  23  24  25  26  27
# 0   0   1   2   3   4   5   6

# 使用参数axis="columns",还可以对于列进行置换
column_sampler = np.random.permutation(7)
df.take(column_sampler,axis="columns")
# 随机选取三行
df.sample(n=3)

# 要通过替换的方式生成样本(允许重复选择),可以在sample中传入replace=True
choices = pd.Series([5,7,-1,6,4])
choices.sample(n=10,replace=True)

计算指标/虚拟变量

另一种常用于统计建模或机器学习的转换方式是将分类变量转换为“虚拟”或“指标矩阵”。如果DataFrame的某一列中含有k个不同的值,则可以派生出一个k列矩阵或DataFrame(其值全为1和0)。pandas有一个pandas.get_dummies函数可以实现该功能。

df = pd.DataFrame({"key":["b","b","a","c","a","b"],"data1":range(6)})
pd.get_dummies(df["key"])  # 0行a有没有值,0行b……,1行a,1行b
#      a      b      c
# 0  False   True  False
# 1  False   True  False
# 2   True  False  False
# 3  False  False   True
# 4   True  False  False
# 5  False   True  False

# pandas.get_dummies的prefix参数添加前缀
dummies = pd.get_dummies(df["key"],prefix="key")
df_with_dummy = df[["data1"]].join(dummies)
#    data1  key_a  key_b  key_c
# 0      0  False   True  False
# 1      1  False   True  False
# 2      2   True  False  False
# 3      3  False  False   True
# 4      4   True  False  False
# 5      5  False   True  False

如果DataFrame中的某行属于多个分类,我们就必须使用另一种方法创建虚拟变量:

mnames = ["movie_id","title","genres"]
movies = pd.read_table("movies.dat",sep="::",
                       header=None,names=mnames,engine="python")

[图片上传失败...(image-13c70a-1745673409999)]

dummies = movies["genres"].str.get_dummies("|")
dummies.iloc[:10,:6]

[图片上传失败...(image-c64ff4-1745673409999)]

使用add_prefix方法给dummies的列名添加前缀“Genre_”,然后就可以像以前一样,将dummies和movies连接起来了:

movies_windic = movies.join(dummies.add_prefix("Genre_"))

将pandas.get_dummies和pandas.cut等离散化函数结合起来使用:

np.random.seed(12345)  # 为了使实例数据是重复的
values = np.random.uniform(size=10)
bins = [0,0.2,0.4,0.6,0.8,1]
pd.get_dummies(pd.cut(values,bins))
#    (0.0, 0.2]  (0.2, 0.4]  (0.4, 0.6]  (0.6, 0.8]  (0.8, 1.0]
# 0       False       False       False       False        True
# 1       False        True       False       False       False
# 2        True       False       False       False       False
# 3       False        True       False       False       False
# 4       False       False        True       False       False
# 5       False       False        True       False       False
# 6       False       False       False       False        True
# 7       False       False       False        True       False
# 8       False       False       False        True       False
# 9       False       False       False        True       False

扩展数据类型

pandas发展出了扩展类型,可以创建NumPy原本不支持的新数据类型。这些新数据类型可以当作NumPy数组的一级类,等同于其他NumPy原生数据

pandas的扩展数据类型:

  • BooleanDtype:可为空的布尔数据,作为字符串传递时使用“boolean
  • CategoricalDtype:分类数据类型,作为字符串传递时使用“category“
  • DatetimeTZDtype:带有时区的Datetime对象
  • Float32Dtype32:位可为空的浮点类型,作为字符串传递时使用“Float32”
  • Float64Dtype64:位可为空的浮点类型,作为字符串传递时使用“Float64”
  • Int8Dtype8:位可为空的有符号整数类型,作为字符串传递时使用“Int8”
  • Int16Dtype16:位可为空的有符号整数类型,作为字符串传递时使用“Int16”
  • Int32Dtype32:位可为空的有符号整数类型,作为字符串传递时使用“Int32”
  • Int64Dtype64:位可为空的有符号整数类型,作为字符串传递时使用“Int64”
  • UInt8Dtype8:位可为空的无符号整数类型,作为字符串传递时使用“UInt8”
  • UInt16Dtype16:位可为空的无符号整数类型,作为字符串传递时使用“UInt16”
  • UInt32Dtype32:位可为空的无符号整数类型,作为字符串传递时使用“UInt32”
  • UInt64Dtype64:位可为空的无符号整数类型,作为字符串传递时使用“UInt64”

字符串操作

Python内置的字符串对象方法

# split拆分成数段
val = "a,b, guido"
val.split(",")
# split常常与strip一起使用,以去除空白符(包括换行符)
pieces = [x.strip() for x in val.split(",")]
# 加号拼接字符串
first,second,third = pieces
first+"::"+second+"::"+third
# join方法传入列表或元组,拼接成字符串
"::".join(pieces)

# 字符串定位;检测子字符串的最佳方式是利用Python的in关键字
"guido" in val   
val.index(",") # 注意find和index的区别,如果找不到字符串,index将会抛出一个异常(而不是返回-1
val.find(":")

# count可以返回指定子字符串的出现次数
val.count(",")

# 替换
val.replace(",","::")
val.replace(",","")

Python的内置字符串方法:

  • count:返回子字符串在字符串中的出现次数(非重叠)
  • endswith:如果字符串以某个后缀结尾,则返回True
  • startswith:如果字符串以某个前缀开头,则返回True
  • join:将字符串用作连接其他字符串序列的分隔符
  • index:如果在字符串中找到子字符串,则返回子字符串第一个字符所在的位置:如果没有找到,则抛出VaLueError
  • find:如果在字符串中找到子字符串,则返回第一个发现的子字符串的第一个字符所在的位置;如果没有找到,则返回-1
  • rfind:如果在字符串中找到子字符串,则返回最后一个发现的子字符串的第一个字符所在的位置;如果没有找到,则返回-1
  • replace:用另一个字符串替换指定子字符串
  • strip、rstrip、Istrip:分别去除字符串两边、右边、左边的空白符(包括换行符)
  • split:通过指定的分隔符将字符串拆分为一组子字符串
  • lower:将字母字符转换为小写形式
  • upper:将字母字符转换为大写形式
  • casefold:将字符转换为小写,并将任何特定区域的变量字符组合转换成常见的可比较形式
  • ljust、rjust:左对齐和右对齐:用空格(或其他字符)填充字符串的相反侧以返回符合最低宽度的字符串

正则表达式

# 描述一个或多个空白符的正则表达式是\s+
# 调用re.split("\s+", text)时,正则表达式会先被编译,然后再在text上调用其split方法
text = "foo   bar\t baz  \tqux"
re.split(r"\s+",text) # ['foo', 'bar', 'baz', 'qux']
# 可以用re.compile自己编译正则表达式,以得到可重复使用的正则表达式对象
regex = re.compile(r"\s+")
regex.split(text)
# 得到匹配正则表达式的所有模式
regex.findall(text) # ['   ', '\t ', '  \t']

# match和search与findall功能类似。findall返回的是字符串中所有的匹配项,而search则只返回第一个匹配项。match更加严格,它只匹配字符串的起始位置
text ="""Dave dave@google.com
Steve steve@gmail.com
Rob rob@gmail.com
Ryan ryan@yahoo.com"""
pattern =r"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}"
# findall将得到一组电子邮件地址
regex = re.compile(pattern=pattern,flags=re.IGNORECASE) # ['dave@google.com', 'steve@gmail.com', 'rob@gmail.com', 'ryan@yahoo.com']

# <re.Match object; span=(5, 20), match='dave@google.com'>
m = regex.search(text)
text[m.start():m.end()] # dave@google.com

# sub方法可以将匹配到的模式替换为指定字符串,并返回所得到的新字符串
regex.sub("REDACTED",text)

# 假设你不仅想找出电子邮件地址,还想将各个地址分成三个部分:用户名、域名以及域名后缀。
# 要实现此功能,只需将待分割的模式的各部分用圆括号括起来即可
pattern = r"([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})"
regex = re.compile(pattern,flags=re.IGNORECASE)
m = regex.match("wesm@bright.net")
m.groups() # ('wesm', 'bright', 'net')
# findall会返回一个元组列表
regex.findall("wesm@bright.net") # [('wesm', 'bright', 'net')]

# ub还能通过诸如\1、\2之类的特殊符号访问各匹配项中的分组
regex.sub(r"Username: \1, Domain: \2, Suffix: \3","wesm@bright.net") # Username: wesm, Domain: bright, Suffix: net

正则表达式方法:

  • findall:返回字符串中所有的非重叠匹配模式的列表
  • finditer:类似findall,返回的是迭代器
  • match:从字符串起始位置匹配模式,还可以对模式各部分进行分组。如果匹配到模式,则返回一个匹配对象,否则返回None
  • search:扫描整个字符串以匹配模式。如果找到则返回一个匹配对象。与match不同,其匹配项可以位于字符串的任意位置,而不仅仅是起始处
  • split:根据找到的模式将字符串拆分为数段
  • sub、subn:将字符串中所有的(sub)或前n个(subn)模式替换为指定表达式。在替换字符串中可以通过1、\2等符号表示各分组项

pandas的字符串函数

dic = {"Dave":"dava@google.com","Steve":"steve@gmail.com","Rob":"rob@gmail.com","Wes":np.nan}
data = pd.Series(dic)
# 通过str.contains检查各个电子邮件地址是否含有“gmail"
data.str.contains("gmail") # 结果中包含object数据类型
# Dave     False
# Steve     True
# Rob       True
# Wes        NaN
# dtype: object

# pandas提供了扩展类型,专门用于处理缺失数据中棘手的字符串、整数、布尔值数据
data_as_string_ext.str.contains("gmail")
# Dave     False
# Steve     True
# Rob       True
# Wes       <NA>
# dtype: boolean

pattern = r"([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})"
data.str.findall(pattern,flags=re.IGNORECASE)
# Dave     [(dava, google, com)]
# Steve    [(steve, gmail, com)]
# Rob        [(rob, gmail, com)]
# Wes                        NaN

# 有多种办法可以实现向量化的元素获取操作。一种是使用str.get,另一种是在str属性上使用索引
matchs = data.str.findall(pattern,flags=re.IGNORECASE).str[0]
# Dave     (dava, google, com)
# Steve    (steve, gmail, com)
# Rob        (rob, gmail, com)
# Wes                      NaN

matchs.str.get(1)
# Dave     google
# Steve     gmail
# Rob       gmail
# Wes         NaN
# dtype: object

# str.extract方法可以用DataFrame的形式返回正则表达式获取的分组
data.str.extract(pattern,flags=re.IGNORECASE)
#            0       1    2
# Dave    dava  google  com
# Steve  steve   gmail  com
# Rob      rob   gmail  com
# Wes      NaN     NaN  NaN

pandas的部分字符串方法:

  • cat:实现元素级的字符串连接操作,可指定分隔符
  • contains:返回表示个字符串是否含有指定模式/正则表达式的布尔型数组
  • count:计算模式的出现次数
  • extract:使用带分组的正则表达式从字符串Series提取一个或多个字符串,结果是一个DataFrame,每组形成一列
  • endswith:等价于对每个元素执行x.endswith(pattern)
  • startswith:等价于对每个元素执行x.startswith(pattern)
  • findall:计算各字符串的所有模式/正则表达式的匹配项,以列表返回
  • get:对各元素进行索引(获取第个元素)
  • isalnum:等价于内置的str.alnum
  • isalpha:等价于内置的str.isalpha
  • isdecimal:等价于内置的str.isdecimal
  • isdigit:等价于内置的str.isdigit
  • islower:等价于内置的str.islower
  • isnumeric:等价于内置的str.isnumeric
  • isupper:等价于内置的str.isupper
  • join:根据指定的分隔符将Series中各元素的字符串连接起来
  • len:计算各字符串的长度
  • Lower, upper:转换大小写。等价于对各个元素执行x.lower()或x.upper()
  • match:根据指定的正则表达式对各个元素执行re.match,返回True或False
  • pad:在字符串的左边、右边或两边添加空白符
  • center:等价于pad(side="both")
  • repeat:重复值。例如,s.str.repeat(3)等价于对各个字符串执行x*3
  • replace:用指定字符串替换模式/正则表达式
  • slice:截取Series中的各个字符串
  • split:根据分隔符或正则表达式对字符串进行拆分
  • strip:去除两边的空白符,包括新行
  • rstrip:去除右边的空白符
  • Istrip:去除左边的空白符

分类数据

背景和目标

values = pd.Series(["apple","orange","apple","orange"]*2)
# 取出唯一值,返回array
pd.unique(values=values)
# 计算频率,返回series
pd.value_counts(values)

# 在数据仓库中,一种好的实践方法是使用包含不同值的维度表,将主要观测值存储为引用维度表的整数键
values = pd.Series([0,1,0,0]*2)
dim = pd.Series(['apple','orange'])
dim.take(values)
# 0     apple
# 1    orange
# 0     apple
# 0     apple
# 0     apple
# 1    orange
# 0     apple
# 0     apple

分类表示可以在进行分析时显著提升性能。你也可以在保持编码不变的情况下,对分类进行转换。一些相对简单的转换示例包括:

  • 重命名分类
  • 加入一个新的分类,不改变已经存在的分类的顺序或位置

pandas的分类扩展类型

pandas有一个特殊的分类类型,用于保存使用整数分类表示法(或编码)的数据。这种对于大量相似值的数据压缩技术,可以显著提升性能并降低内存占用,尤其是对于字符串数据。

fruits = ['apple','orange','apple','apple']*2
N=len(fruits)
rng = np.random.default_rng(seed=12345)
df = pd.DataFrame({
    'fruit':fruits,
    'basket_id':np.arange(N),
    'count':rng.integers(3,15,size=N),
    'weight':rng.uniform(0,4,size=N)},
    columns=['basket_id','fruit','count','weight'])
#    basket_id   fruit  count    weight
# 0          0   apple     11  1.564438
# 1          1  orange      5  1.331256
# 2          2   apple     12  2.393235
# 3          3   apple      6  0.746937
# 4          4   apple      5  2.691024
# 5          5  orange     12  3.767211
# 6          6   apple     10  0.992983
# 7          7   apple     11  3.795525
fruit_cat = df['fruit'].astype('category')  # fruit_cat的值就变成了pandas.Categorical实例
# 0     apple
# 1    orange
# 2     apple
# 3     apple
# 4     apple
# 5    orange
# 6     apple
# 7     apple
# Name: fruit, dtype: category
# Categories (2, object): ['apple', 'orange']
c = fruit_cat.array
# Categorical对象有categories和codes两个属性
c.categories # Index(['apple', 'orange'], dtype='object')
c.codes # [0 1 0 0 0 1 0 0]
# 得到编码和分类的映射
dict(enumerate(c.categories)) # {0: 'apple', 1: 'orange'}
# 过赋值为转换后的结果,可将DataFrame的列转换为分类
df['fruit'] = df['fruit'].astype('category')

# 从Python序列直接创建pandas.Categorical
my_categories = pd.Categorical(['foo','bar','baz','foo','bar'])

# 还可以使用from_codes构造器
categories = ['foo','bar','baz']
codes = [0,1,2,0,0,1]
my_cats_2 = pd.Categorical.from_codes(codes,categories)

# 指定顺序
order_cat = pd.Categorical.from_codes(codes,categories,ordered=True) # [foo<bar<baz]指明'foo'位于'bar'之前,以此类推
# 0     apple
# 1    orange
# 2     apple
# 3     apple
# 4     apple
# 5    orange
# 6     apple
# 7     apple
# Name: fruit, dtype: object
# ['foo', 'bar', 'baz', 'foo', 'foo', 'bar']
# Categories (3, object): ['foo' < 'bar' < 'baz']

# 无序的分类实例可以通过as_ordered排序
my_cats_2.as_ordered()

使用Categorical对象进行计算

来看一些随机的数值数据,并使用pandas.qcut分箱函数。结果会返回pandas.Categorical,我们之前使用过pandas.cut,但没有解释分类是如何工作的:

rng = np.random.default_rng(seed=12345)
draws = rng.standard_normal(1000)
# 计算该数据的四分位分箱,并提取一些统计信息
bins = pd.qcut(draws,4)
# 参数labels添加标签名
bins = pd.qcut(draws,4,labels=['Q1','Q2','Q3','Q4'])
# ['Q1', 'Q4', 'Q1', 'Q2', 'Q2', ..., 'Q3', 'Q3', 'Q2', 'Q3', 'Q2']
# Length: 1000
# Categories (4, object): ['Q1' < 'Q2' < 'Q3' < 'Q4']

bins.codes[:10] # [0 3 0 1 1 0 0 2 2 0]
# 带有标签的bins分类不包含数据分箱的边界信息,因此可以使用groupby提取一些汇总统计信息
bins = pd.Series(bins,name='quartile')
result = (pd.Series(draws)
          .groupby(bins)
          .agg(['count','min','max'])
          .reset_index())
#   quartile  count       min       max
# 0       Q1    250 -3.119609 -0.678494
# 1       Q2    250 -0.673305  0.008009
# 2       Q3    250  0.018753  0.686183
# 3       Q4    250  0.688282  3.211418

# 结果中的'quartile'列保留了bins中原始的分类信息,包括排序
result['quartile']
# 0    Q1
# 1    Q2
# 2    Q3
# 3    Q4
# Name: quartile, dtype: category
# Categories (4, object): ['Q1' < 'Q2' < 'Q3' < 'Q4']
使用分类提高性能
N = 10_000_000
labels = pd.Series(['foo','bar','baz','qux']*(N//4))
# 将labels转换为分类
categories = labels.astype("category")  # 转换为分类不是没有代价的,但代价是一次性的
labels.memory_usage(deep=True) # 520000132
categories.memory_usage(deep=True) # 10000512

使用分类对象进行GroupBy操作明显更快,这是因为底层的算法使用了整数编码的数组,而不是字符串数组。

分类方法

s = pd.Series(['a','b','c','d']*2)
cat_s = s.astype("category")
# 用set_categories方法进行修改分类
actural_categories = ['a','b','c','d','e']
cat_s2 = cat_s.cat.set_categories(actural_categories)

# 使用remove_unused_categories方法删除未观察到的分类
cat_s3 = cat_s[cat_s.isin['a','b']]
# 0    a
# 1    b
# 4    a
# 5    b
# dtype: category
# Categories (4, object): ['a', 'b', 'c', 'd']
cat_s3.cat.remove_unused_categories()
# 0    a
# 1    b
# 4    a
# 5    b
# dtype: category
# Categories (2, object): ['a', 'b']

pandas中Series的分类方法:

  • add_categories:在已存在的分类后面添加新的(未使用的)分类
  • as_ordered:使分类有序
  • as_unordered:使分类无序
  • remove_categories:移除分类,设置任何被移除的值为空
  • remove_unused_categories:移除任意没有出现在数据中的分类值
  • rename_categories:用指定的新分类名称替换旧名称:不能改变分类的数目
  • reorder_categories:与rename_categories很像,但可以修改结果使分类有序
  • set_categories:用指定的新分类名称替换旧名称;可以添加或删除分类
为建模创建虚拟变量
cat_s = pd.Series(['a','b','c','d']*2,dtype='category')
pd.get_dummies(cat_s)
#        a      b      c      d
# 0   True  False  False  False
# 1  False   True  False  False
# 2  False  False   True  False
# 3  False  False  False   True
# 4   True  False  False  False
# 5  False   True  False  False
# 6  False  False   True  False
# 7  False  False  False   True

相关文章

网友评论

      本文标题:数据清洗和准备

      本文链接:https://www.haomeiwen.com/subject/cpwobjtx.html