数据清洗与准备
1. 处理缺失值
缺失值:np.nan;None;
1.1 过滤缺失值——dropna
- Series 对象:
from numpy import nan as NA
data = pd.Series([1, NA, 3.5, NA, 7])
data.dropna() == data[data.notnull()] #两种过滤方法是等价的
- DataFrame 对象:
data = pd.DataFrame([[1., 6.5, 3.], [1., NA, NA], [NA, NA, NA], [NA, 6.5, 3.]])
cleaned_1 = data.dropna() #默认删除包含缺失值的行
cleaned_2 = data.dropna(axis=1) #删除包含缺失值的列
cleaned_3 = data.dropna(how='all') #删除所有值都为 NA 的行
cleaned_4 = data.dropna(thresh=2) #设置阈值,保留含有2个及以下NA值的行
1.2 补全缺失值——fillna
fill_1 = df.fillna(0) #使用一个常数来替换缺失值
fill_2 = df.fillna({1: 0.5, 2: 0}) #为不同的列设定不同的填充值
fill_3 = df.fillna(0, inplace=True) #fillna 默认返回新的对象,使用 inplace 参数修改原对象
fill_4 = df.fillna(method='ffill', limit=2) #向前插值(默认值),(bfill为向后插值)并设定最大填充范围
fill_5 = df.fillna(method='bfill', axis=1) #axis默认为0,按行插值,1为按列插值
2. 数据转换
2.1 删除重复值
df = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]})
is_duplicated = df.duplicated() #返回一个布尔型 Series,反映每一行是否重复
droped = df.drop_duplicates() #返回数组中未重复的部分
# 以上都是默认对列进行操作
droped_1 = df.drop_duplicates('k1') #基于'k1'列去除重复值
droped_last = df.drop_duplicates(['k1'], keep='last') #默认是保留第一个观测到的值,keep='last'指定返回最后一个
2.2 使用函数或者映射进行数据转换——map
'''关于肉类的假设数据,需求添加一列用于表明每种食物的动物类型'''
data = pd.DataFrame({'food': ['bacon', 'pulled pork', 'bacon',
'Pastrami', 'corned beef', 'Bacon',
'pastrami', 'honey ham', 'nova lox']})
# 使用字典映射
meat_to_animal = {
'bacon': 'pig',
'pulled pork': 'pig',
'pastrami': 'cow',
'corned beef': 'cow',
'honey ham': 'pig',
'nova lox': 'salmon'
}
data['animal'] = data['food'].str.lower().map(meat_to_animal)
# 使用匿名函数
data['animal'] = data['food'].map(lambda x:meat_to_animal[x.lower()])
2.3 替代值——replace
data= pd.Series([1., -999, 2, -999, -1000, 3])
replaced_1 = data.replace(-999, np.nan) #使用 NA 替代 -999
replaced_2 = data.replace([-999, -1000], np.nan) #一次替代多个值
replaced_3 = data.replace([-999, -1000], [np.nan, 0]) #不同值替代不同值
replaced_4 = data.replace({-999: np.nan, -1000: 0}) #也可使用字典作为参数传递
2.4 重命名索引
- 使用函数或映射——map:
data = pd.DataFrame(np.arange(12).reshape((3, 4)),
index = ['Ohio', 'Colorado', 'New York'],
columns = ['one', 'two', 'three', 'four'])
transform = lambda x: x[:4].upper()
data.index = data.index.map(transform) # 在原数组上修改索引
- 使用 rename:默认不修改原数据
data_1 = data.rename(index=str.title, columns=str.upper) #使用函数转换
data_2 = data.rename(index={'OHIO': 'INDIANA'},
columns = {'three': 'peekaboo'}, inplace=True) #结合字典型对象,为轴标签的子集提供新的值,并修改原数组
2.5 离散化和分箱——cut & qcut
- cut 的基本用法:
'''将一群人的年龄分组'''
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
bins = [18, 25, 35, 60, 100] #分组的区间
cats = pd.cut(ages, bins)
'''输出如下:
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
'''
cats.codes
'''输出如下:
array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
'''
cats.categories
'''输出如下:
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]],
closed='right',
dtype='interval[int64]')
'''
counts = pd.value_counts(cats) #各箱中包含的值的数量
'''输出如下:
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
dtype: int64
'''
返回的是一个 Categories 对象,可以当作是一个表示箱名的字符串数组,每个值代表原数组中所对应的值处于分组的哪个区间;codes 属性表示每个值代表原数组中所对应的值处于分组的第几个区间,categories 属性表示区间的数据标签
- cut 默认区间的左侧开放(小括号),右侧封闭(中括号,包括边),指定哪边封闭:
cut_left = pd.cut(ages, bins, right=False)
- labels 参数传入自定义箱名
group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
cut_with_names = pd.cut(ages, bins, labels=group_names)
- 传入整数代替显式的箱边,根据值的最大值和最小值来以及箱的个数来决定分组区间(均匀分布的数据会使每个箱具有相同数量的数据,其他分布一般来说不会)
data = np.random.rand(20)
four_box = pd.cut(data, 4, precision=2) #precision=2 选项将十进制精度限制在两位
- qcut 的用法:qcut 基于样本分位数进行分箱,因此 qcut 可获得等长的箱
data = np.random.randn(1000)
cats = np.qcut(data, 4) # 切成 4 份
cats_2 = pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.]) #自定义分位数
2.6 检测和过滤异常值
'''检测数组中绝对值大于 3 的值'''
data = pd.DataFrame(np.random.randn(1000, 4))
col = data[1]
col_3 = col[np.abs(col) > 3] #找出一列中绝对值大于 3 的值
data_3 = data[(np.abs(data) > 3).any(1)] #找出所有含有绝对值大于 3 的行,axis=1 代表沿着列标签执行方法(消灭列标签)
data[np.abs(data) > 3] = np.sign(data) * 3 #np.sign(data)根据data的值的正负生成-1与1
2.7 置换和随机抽样
- 使用 np.random.permutation 对 DataFrame 中的行进行置换(随机重排序):
df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))
sampler = np.random.permutation(5) #产生一个长度为5的表示新顺序的整数数组
df.take(sampler) #整数数组可以用在基于 iloc 的索引或等价的 take 函数中,以实现对原数组的重排序
sample_1 = df.sample(n=3) #随机抽3行,不能重复选择
sample_2 = df.sample(n=4, replace=True) #允许重复选择
2.8 计算指标/虚拟变量
- 假设 DataFrame 中的一列有 k 个不同的值,使用 get_dummies 函数衍生一个 k 列值为 1 和 0 的矩阵或 DataFrame:
df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'c'],
'data1': range(6)})
dummies = pd.get_dummies(df['key'])
'''输出如下:
a b c
0 0 1 0
1 0 1 0
2 1 0 0
3 0 0 1
4 1 0 0
5 0 0 1
'''
- 在指标 DataFrame 的列上加入前缀,然后与其他数据合并:
dummies_2 = pd.get_dummies(df['key'], prefix='key')
df_with_dummy = df[['data1']].join(dummies_2)
'''输出如下:
data1 key_a key_b key_c
0 0 0 1 0
1 1 0 1 0
2 2 1 0 0
3 3 0 0 1
4 4 1 0 0
5 5 0 0 1
'''
- 将 get_dummies 和 cut 等离散化函数结合使用:
values = np.random.rand(10)
bins = [0, 0.2, 0.4, 0.8, 1]
dummies = pd.get_dummies(pd.cut(values, bins))
3. 字符串操作
3.1 字符串对象方法
方法 | 描述 |
---|---|
str_1.count(str_2) | 返回子字符串(str_2)在字符串(str_1)中的非重叠出现次数 |
str_1.endswith(str_2) | 如果 str_1 以 str_2 结尾则返回 True |
str_1.startswith(str_2) | 如果 str_1 以 str_2 开头则返回 True |
str.join(list) | 使用 str_1 作为间隔符,用于粘合字符串序列 list |
str_1.index(str_2) | 如果在 str_1 中找到子字符串 str_2,则返回 str_2 中第一个字符的位置;如果找不到则引发 ValueError |
str_1.find(str_2) | 返回 str_1 中第一个出现 str_2 的第一个字符的位置,与 index 类似,但找不到则返回 -1 |
str_1.rfind(str_2) | 与 find 类似,但返回最后一个出现 str_2 的第一个字符的位置 |
str.replace(old_str, new, max) | 使用 new 替换 str 中的 old_str,可选参数 max 指定最多替换次数 |
str.strip(), str.rstrip(), str.lstrip() | 修剪空白,包括换行符;相当于对每个元素进行x.strip()(包括 rstrip, lstrip) |
str_1.split(str_2) | 使用 str_2 分隔符将字符串 str_1 拆分为子字符串的列表 |
str.lower() | 大写字母转换为小写字母 |
str.upper() | 小写字母转换为大写字母 |
str.casefold() | 将字符转换为小写,适用于所有存在大小写的字符 |
str.ljust(int), str.rjust(int) | 左对齐或右对齐;用空格(或一些其他字符)填充字符串的相反侧以返回具有最小宽度的字符串 |
3.2 正则表达式——re 模块
- 例:将含有多种空白字符(制表符、空格、换行符)的字符串拆分开:
import re
text = 'foo far\t baz \tqux'
result = re.split('\s+', text) #描述一个或多个空白字符的正则表达式是 \s+
# result = ['foo', 'far', 'baz', 'qux']
'''调用 re.split('\s+', text) 时正则表达式会首先被编译,然后正则表达式的 split 方法在
传入文本上被调用。可以使用 re.compile 自行编译,形成一个可复用的正则表达式对象
'''
regex = re.compile('\s+')
result = regex.split(text) #得到的结果和上面的方法一样
result_all = regex.findall(text) #返回一个所有匹配正则表达式的模式的列表
# result_all = [' ', '\t ', ' \t']
- 正则表达式方法
方法 | 描述 |
---|---|
findall | 将字符串中所有的非重叠匹配模式以列表形式返回 |
finditer | 与 findall 类似,但返回的是迭代器 |
match | 在字符串起始位置匹配模式,也可以将模式组建匹配到分组中;如果模式匹配上了,返回一个匹配对象,否则返回 None |
search | 扫描字符串的匹配模式,如果扫描到了返回匹配对象,与 match 方法不同的是,search 方法的匹配可以是字符串的任意位置,而不仅仅是字符串的起始位置 |
split | 根据模式,将字符串拆分为多个部分 |
sub, subn | 用替换表达式替换字符串中所有的匹配(sub)或第n个出现的匹配串(subn);使用符号 \1, \2, … 来引用替换字符串中的匹配组元素 |
3.3 pandas 中的向量化字符串函数
部分向量化字符串方法列表
方法 | 描述 |
---|---|
cat | 根据可选的分隔符按元素黏合字符串 |
contains | 返回是否含有某个模式/正则表达式的布尔值数组 |
count | 模式出现次数的计数 |
extract | 使用正则表达式从字符串 Series 中分组抽取一个或多个字符串,返回的结果是每个分组形成一列的 DataFrame |
endswith | 等价于对每个元素使用 x.endswith(模式) |
starswith | 等价于对每个元素使用 x.startswith(模式) |
findall | 找出字符串中所有的模式/正则表达式匹配项,以列表返回 |
get | 对每个元素进行索引(获得第 i 个元素) |
isalnum | 等价于内建的 str.alnum |
isalpha | 等价于内建的 str.isalpha |
isdecimal | 等价于内建的 str.isdecimal |
isdigit | 等价于内建的 str.isdigit |
isnumeric | 等价于内建的 str.isnumeric |
isupper | 等价于内建的 str.isupper |
join | 根据传递的分隔符,将Series中的字符串联合 |
len | 计算每个字符串的长度 |
lower, upper | 转换大小写,等价于对每个元素进行 x.lower() 或 x.upper() |
match | 使用 re.match 将正则表达式应用到每个元素上,将匹配分组以列表形式返回 |
pad | 将空白加到字符串左边、右边或两边 |
center | 等价于 pad(side=‘both’) |
repeat | 重复值(如 s.str.repeat(3) 等价于对每个字符串进行 *3) |
replace | 以其他字符串替代模式/正则表达式的匹配项 |
slice | 对 Series 中的字符串进行切片 |
split | 以分隔符或正则表达式对字符串进行拆分 |
strip | 对字符串两侧的空白进行消除,包括换行符 |
rstrip | 消除字符串右边的空白 |
lstrip | 消除字符串左边的空白 |