数据清洗和准备
处理缺失数据
# 对于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
网友评论