数据规整化
重塑和轴向旋转
对表格型数据重新排列
重塑层次化索引
stack: 将数据的列旋转为行
unstack: 将数据的行旋转为列
- data:
- number one two three
- state
- Ohio 0 1 2
- Colorado 3 4 5
data.stack()结果如下: 为一个 Series
- state number
- Ohio one 0
- two 1
- three 2
- Colorado
- one 3
- two 4
- three 5
反过来就是用 unstack()
将长格式旋转为宽格式
长格式为全部列像关系型数据库 (如 MySQL) 存储, 固定架构, 后续增加数据或者删除, 数据只会越来越长但是缺点是数据操作不方便因为都是一行做一个数据, 看上去数据就特别多, 但是如果将一个作为索引, 就能减少一定量的数据级, 但是数据量并没减少
- pivoted = ldata.pivot('date', 'item', 'value')
- # 用 DataFrame 的 pivot 的方法实现 date 作行索引, item 做列, value 为值
- # 但是 pivot 只是一个快捷方式, 并没有改变源数据的格式, 所以如果需要改变源格式, 需要结合上面重塑层次化索引来操作:
- unstacked = ldata.set_index(['date','item']).unstack('item')
数据转换
前面提到的是对数据进行重排, 另一类重要操作则是过滤清理以及其他的
转换工作
移除重复操作
DataFrame 中出现了重复行时:
- data:
- k1 k2
- o one 1
- I one 1
- 2 one 2
- 3 two 3
- 4 two 3
- 5 two 4
- 6 two 4
- # 返回每行是否为重复行(与之前的列比较, 所以第一次出现时是 false, 第二次及之后就是 true 了)
- data.duplicated() :
- 0 False
- 1 True
- 2 False
- 3 False
- 4 True
- 5 False
- 6 True
- # 返回移除了重复行的 DataFrame(默认判断所有列)
- data.drop_duplicates()
- # 返回移除了重复行的 DataFrame(只判断某列)
- data.drop_duplicates(['k1'])
- #drop_duplicates 默认保留的是第一次出现的组合, 传入 take_last=True 则保留最后一个:
- data.drop_duplicates(['k1','k2'],take_last=True)
利用函数或映射进行数据转换
假设一个 data 如下:
- data:
- food ounces
- 0 bacon 4.0
- 1 pulled pork 3.0
- 2 bacon 12.0
- 3 Pastrami 6.0
- 4 corned beef 7.5
- S Bacon 8.0
- 6 pastrami 3.0
- 7 honey ham 5.0
- 8 nova lox 6.0
如果想添加一列表明该 food 来源的动物类型, 需要先编写一个肉类到动物的映射:
- meat_to_animal = {
- 'bacon': 'pig',
- 'pulled pork': 'pig',
- 'pastrami': 'cow',
- 'corned beef': 'cow',
- 'honey ham': 'pig',
- 'nova lox': 'salmon'
- }
如何增加上去, 注意到有的肉类有大写, str.lower 为一个函数, 可以放在 Series 的 map 方法中, 再把映射也可以放进来:
data['animal'] = data['food'].map(str.lower).map(meat_to_animal)
而使用 lambda 则更简洁:
data['animal'] = data['food'].map(lambda x:meat_to_animal[x..lower()])
替换值
之前处理缺失时, 使用 fillna 方法填充可以看作是一种特殊情况, 前面的 map 当然也可以用来替换, 但是 replace 是一种更方便灵活的方法
- # 把 data 的 - 999 替换为缺失值
- 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})
重命名轴索引
对轴索引重新命名, 也可以使用 map 来传入一个函数等操作
- # 如下 对 index 索引名字大写
- data.index = data.index.map(str.upper)
如果想创建数据集的转换版而不是修改原始数据, 则可以使用 rename:
- data.rename(index = str.title,columns =str.upper)
- #index 首字母大写, columns 全部大写
- # 传入字典即可对部分轴标签替换'OHIO'替换为'INDIANA','three'替换'peekaboo'
- data.rename(index={'OHIO': 'INDIANA'},
- columns={'three': 'peekaboo'})
rename 不是修改源数据, 如果想直接修改, 在 rename 内直接传入 inplace=True 即可
离散化和面元划分
我的理解就是对一组数据划分到不同的组内:
假设有一组人员数据, 而你希望将它们划分为不同的年龄组
In [153]: ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
接下来将这些数据划分为 18 到 25""26 到 35""35 到 60 以及 60 以上几个面元要实现该功能. 需要使用 panda 的 cut 函数:
- In [154]: bins = [18, 25, 35, 60, 100]
- In [155]: cats = pd.cut(ages, bins)
- In [156]: cats
- Out[156]:
- Lategorlcal:
- array([(18, 25], (18, 25], (18, 25), (25, 35], (18, 25], (18, 25],
- (35, 60], (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]], dtype=object)
- Levels (4): Index([(18, 25], (25, 35], (35, 60], (60, 100]], dtype=object)
实际上是先定义一个 labels 指明每个值分别位于哪个区间(labels), 然后对区间进行替换显示(levels)
- cats.labels
- array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1])
默认区间为左开右闭, 可以通过
pd.cut(ages, [18, 26, 36, 61, 100], right=False)
来修改为左闭右开这个 labels 负责最后的显示, 所以
- group names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
- pd.cut(ages, bins, labels=group_names)
- # 最后就不再是输出 (18, 25] 这种, 而是输出 Youth,YoungAdult 等
如果传入面元数量(分组数量), 则会根据数据最小值和最大值计算等长面元
- # 将一些均匀分布数据分为四组
- data = np.randam.rand(20)
- pd.cut(data, 4, precision=2)
qcut 是类似 cut 的函数, 但是 qcut 是使用的样本分位数, 所以得到大小基本相等的面元即每个面元, 每个分组的数据数量基本相同 pd.qcut(data,4)即将数据均匀撒在四个分组中
- # 当然也不一定每个分组的数据数量要相同
- pd.qcut(data, [0, 0.1, 0.5, 0.9, 1."];
- # 就是按照 1:4:4:1 比例来分组
检测和过滤异常值
- # 找出某列中绝对值大于 3 的值:
- col=data[3]
- col[np.abs(col)>3]
- # 选出所有含有超过 3 或 - 3 的值的行, 可以利用布尔型 DataFrame 以及 any 方法:
- data[(np.abs(data) > 3).any(1)]
- # 将值限制在区间 - 3 到 3 以内:(大于 3 的取 3 小于 - 3 的取 - 3)
- data[np.abs(data) > 3] = np.sign(data) *3
- #sign 为取符号正数为 1 负数为 - 1 0 为 0
排列和随机采样
- #reshape(5,4)分成 5 行 4 列
- df = DataFrame(np.arange(5 * 4).reshape(5, 4))
- #permutation(5)排列
- sampler = np.random.permutation(5)
- sampler:
- array((1, 0, 2, 3, 4])
- df.take(sampler)
- # 即会把 df 的第 1 行和第 0 行互换排列
从 permutation 返回的数组中切下前 3 个元素
- df.take(np.random.permutation(len(df))[:3])
- # 从一个数组里, 随机取 10 个里面的值:
- bag = np.array((S, 7, -1, 6, 4])
- sampler = np.random.randint(0, len(bag), size=10)
- sampler:
- array([4, 4, 2, 2, 2, 0, 3, 0, 4, 1])
- draws = bag.take(sampler)
- draws:
- array([ 4, 4, -1, -1, -1, 5, 6, 5, 4, 7])
计算指标 / 哑变量
DataFrame 的某一列中含有 k 个不同的值, 则可以派生出一个 k 列矩阵或 DataFrame(其值全为 1 和 0) .pandas 有一个 get _dummies 函数可以实现该功能
- df = DataFrame({'key':['b', 'b', 'a', 'c', 'a','b'],'data1': range(6)})
- 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 1 0
DataFrame 列加上一个前缀, abc 即变为 key_a,key_b,key_c
dummies = pd.get_dummies(df['key']), prefix='key')
字符串操作
Python 能够成为流行的数据处理语言, 部分原因是其简单易用的字符申和文本处理功能大部分文本运算都直接做成了字符串对象的内置方法对于更为复杂的模式匹配和
文本操作则可能需要用到正则表达式 pandas 对此进行了加强它使你能够对整组数据应用字符串表达式和正则表达式. 而且能处理烦人的缺失数据
字符串对象方法
split,strip 等这些内置的字符串方法已经满足来了大部分要求
pieces=['a', 'b', 'guido']
利用加法, 可以将这些子字符串以双冒号分隔符的形式连接起来:
- first, second, third = pieces
- first + '::'+ second +'::'+ third
- 'a::b::guido'
但是 python 则可以直接:'::'.join(pieces)
等等, 其他内置方法不再赘述使用时百度查表即可
正则表达式
正则表达式觉得是一种很高级简洁的方法, 且其他地方用的也很多, 如果不了解, 建议搜索查看一遍, 有所印象掌握常用的一些用法
pandas 中矢量化的字符串函数
通过 data.map, 所有字符串和正则表达式方法都能被应用于 (传人 lambda 表达式或其他函数) 各个值, 但是如果存在 NA 就会报错为了解决这个问题. Series 有一些能够跳过 NA 值的字符串操作方法通过 Series 的 str 属性即可访问这些方法例如, 我们可以通过 str.contains 检查各个电子邮件地址是否含有 gmail":
data.str.contains('gmail')
有两个办法可以实现矢量化的元素获取操作: 要么使用 str.get, 要么在 str 属性上使用
索引
matches = data.str.match(pattern, flags=re.IGNORECASE);
矢量化的字符串方法如下:
方法 | 说明 |
---|---|
cat | 实现元素级的字符串连接操作,可指定分隔符 |
contains | 返回表示各字符串是否含有指定模式的布尔型数组 |
count | 模式的出现次数 |
endswith, startswith | 相当于对各个元素执行 x.endswith(pattern) 或 x.startswith (pattern) |
findall | 计算各字符串的模式列表 |
get | 获取各元素的第 i 个字符 |
join | 根据指定的分隔符将 Series 中各元紊的字符串连接起来 |
len | 计算各字符串的长度 |
lower, upper | 转换大小写。相当于对各个元素执行 x.lower() 或 x.upper() |
match | 根据指定的正则表达式对各个元素执行 re.match |
pad | 在字符串的左边、右边或左右两边添加空白符 |
center | 相当于 pad(side='both') |
repeat | 重复值。例如,s.str.repeat(3) 相当于对各个字符串执行 x*3 |
replace | 用指定字符串替换找到的模式 |
slice | 对 Series 中的各个字符串进行子串截取 |
split | 根据分隔符或正则表达式对字符串进行拆分 |
strip. rstrip, lstrip | 去除空白符,包括换行符。相当于对各个元素执行 x.strip(), x.rstripp, x.lstrip() |
总结
数据规整化到这章结束, 下一步就是数据可视化
来源: https://juejin.im/post/5a9abe35f265da23815514a0