Pandas进阶贰 pandas基础

Pandas进阶贰 pandas基础

pandas进阶系列根据datawhale远昊大佬的joyful pandas教程写一些自己的心得和补充,本文部分引用了原教程,并参考了《利用Python进行数据分析》、numpy官网、pandas官网
为了方便自己回顾和助教审阅,每一节先将原教程重要知识点罗列一下帮助自己回顾,在其后写一些自己的心得以及我的习题计算过程
本文的的函数总结中,
库函数使用 包名+函数名 命名,如pd.read_csv()
类方法用 类名简写或x + 函数名 命名,如df.to_csv()

另注:本文是对joyful pandas教程的延伸,完整理解需先阅读joyful pandas教程第二章

TODO:目前还有一道习题没做,已经做的习题还没总结方法及与答案做比较,21号晚上补

补充内容

在上一个教程结束后我翻阅了《利用python进行数据分析》进一步学习了numpy相关的内容,有一些收获,暂时先放在这一章和大家分享

notebook 操作

经过最近的学习,对notebook的操作更快了些,主要是记了几个快捷键,让自己效率大大提升,和大家快速分享一下,通过几个快捷键解放双手再也不用鼠标

  • notebook的每个单元格的读写模式和vim有些类似,使用 ESC 切换成命令模式,使用 ENTER 切换成编辑模式
  • 在命令模式下,可以通过 m 转换单元格为Markdown单元格,y 将单元格转换为代码单元格
  • 在命令模式下,可以通过 a 在当前单元格上方新建单元格, b 在当前单元格下方新建单元格
  • 另外除了常用的shift+enter, ctrl+enter外,还有 alt+enter 可以在运行当前单元格后在下方新建一个单元格也很常用
  • 在命令模式下,通过 d d (和vim删除一行的操作一样)可以删除当前单元格,使用 z 可以撤销删除,试了下z也可以撤销好几次的删除,上限有多少不清楚(我试了下撤销11次都没问题)不查不知道,之前误删了好多次,都不知道用z然后就像个傻子一样再打一遍…
  • 在命令模式下,键入 1,2,3... 可以在第一行直接加入一级、二级、三级…标题

这些是我现在经常用到的命令,这些小技巧有点基于个人经验而谈,不过记住这些命令就不用鼠标了打起来很流畅。
更多notebook快捷键可以在命令模式下按 h 查询

numpy数组索引原理

在上一节的练习题中,远昊大佬的习题让我们充分感受到了numpy的高效与灵活,那么为什么numpy可以计算得这么快呢?
首先,np.ndarray与python的list相比,ndarray中的所有数据都是相同类型的,而list中的数据可以是各种类型的,因此ndarray效率更高;
另外也是非常重要的一点是,ndarray的本质是一个数据块的视图
我们通过观察ndarray的内部结构来详细了解以下,ndarray这个类的属性包含以下这些(链接中有全部属性,这里我摘抄一些重点的):

attributes description
strides 跨到下一个元素所需要的字节数
size 数组中元素数量
dtype 数据类型
data 数据块的起始位置

学过C/C++的同学有没有熟悉的感觉!这些属性感觉就是新建了一个数组,然后建了一个指向数组元素类型的指针嘛!(我是这么觉得的,助教大大可以审阅一下看对不对)

因此ndarray的索引和切片并不是新开辟了一个内存空间去存数据,而只是一种视图,即改变了原有数据的访问方式。

具体举个例子验证一下:
现有数组a,数组b的索引方式是b=a[2:8:2],即b是a的第三个元素、第五个元素、第七个元素,按照刚刚的想法,b的实现逻辑应该是根据data、strides和b给出的起始索引,将指针移到第一个要读取的元素的位置,将这个位置赋值给b的data,紧接着根据步长和strides,决定b的每个元素要读取的步长,赋值给b的strides,所以生成b的时候完全没有数据的迁移,仅仅是根据a的属性生成了b的属性
下面的例子验证了这个想法

import numpy as np
import pandas as pd
a = np.arange(10)
b = a[2:8:2]
b[-1] = 999
print(f'a strides: {a.strides}')
print(f'b strides: {b.strides}')
a
a strides: (8,)
b strides: (16,)





array([  0,   1,   2,   3,   4,   5, 999,   7,   8,   9])

Pandas 文件读写

函数总结

function description
pd.read_csv()
pd.read_table() 默认以\t为分隔符,可以自定义
df.to_csv() 默认csv,可以自定义分隔符
df.to_markdown() 天哪还有这种神奇函数(需要安装tabulate包)
常用参数
index_col 用作索引的列号列名
usecols 选择列
header 定义列名
nrows 读取前n行
parse_dates 将某些列解析为datetime

我目前经常会用到的读函数就是read_csv,不过根据read_table给出的参数sep, 说明read_csv也可以通过read_table实现,展示一下:

df_txt = pd.read_table('../data/my_csv.csv', sep=',')
df_txt
col1 col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7

另外可以看出远昊大佬构造的表可谓用心良苦,每一列的数据类型由常识来判断都是不同的,通过常识判断,这五列在一般上下文中的数据类型应该是整形、字符型、浮点数、字符串、日期,那在不做任何预处理的情况下,看看pandas是如何认识数据的:

df_txt.info()

RangeIndex: 4 entries, 0 to 3
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   col1    4 non-null      int64  
 1   col2    4 non-null      object 
 2   col3    4 non-null      float64
 3   col4    4 non-null      object 
 4   col5    4 non-null      object 
dtypes: float64(1), int64(1), object(3)
memory usage: 288.0+ bytes

可以看出,对于整形和浮点型,pandas默认将其标记为int64float64, 而其他类型一概标记为object,因此在具体项目中对其他数据要分别定义好数据类型,对整形和浮点型应该根据语义或者数据,确定其具体的更小的数据类型以压缩数据。

数据写入
一般在数据写入中,最常用的操作是把index设置为False,特别当索引没有特殊意义的时候,这样的行为能把索引在保存的时候去除。
这点要特别注意,如果不设置为False,读取时会多一个Unnamed:0列,我打比赛做特征工程的时候多次犯了这个错误提醒大家特别要小心:(
示例如下:

df_csv.to_csv('../data/my_csv_saved.csv')
df_unindexed = pd.read_csv('../data/my_csv_saved.csv')
df_unindexed.head(1)
Unnamed: 0 col1 col2 col3 col4 col5
0 0 2 a 1.4 apple 2020/1/1

Pandas 基本数据结构

pandas中具有两种基本的数据存储结构,存储一维valuesSeries和存储二维valuesDataFrame,在这两种结构上定义了很多的属性和方法。

1. Series

Series一般由四个部分组成,分别是序列的值data、索引index、存储类型dtype、序列的名字name。其中,索引也可以指定它的名字,默认为空。

s = pd.Series(data = [100, 'a', {
     'dic1':5}],
              index = pd.Index(['id1', 20, 'third'], name='my_idx'),
              dtype = 'object',
              name = 'my_name')
s
my_idx
id1              100
20                 a
third    {'dic1': 5}
Name: my_name, dtype: object
s.values
array([100, 'a', {'dic1': 5}], dtype=object)
s.index
Index(['id1', 20, 'third'], dtype='object', name='my_idx')

这里特别注意的是,
values, index, dtype, name, shape等都是pd.Series类的属性而不是方法,因此调用时没有括号

2. DataFrame

DataFrameSeries的基础上增加了列索引,一个数据框可以由二维的data与行列索引来构造:

data = [[1, 'a', 1.2], [2, 'b', 2.2], [3, 'c', 3.2]]
df = pd.DataFrame(data = data,
                  index = ['row_%d'%i for i in range(3)],
                  columns=['col_0', 'col_1', 'col_2'])
df
col_0 col_1 col_2
row_0 1 a 1.2
row_1 2 b 2.2
row_2 3 c 3.2

特别注意的是
DataFrame中可以用[col_name][col_list]来取出相应的列与由多个列组成的表,结果分别为SeriesDataFrame

以下和原文示例稍有差别,主要展示即使只选取一列也可以构造DataFrame,只需要使用col_list

type(df['col_0'])
pandas.core.series.Series
type(df[['col_0']])
pandas.core.frame.DataFrame

三、常用基本函数

function description
1.汇总函数
head,tail 预览首,尾n行
info 表的信息概括
describe 表各列的统计概括
pandas-profiling包 更全面的数据汇总
2,特征统计函数(聚合函数)
sum,mean,std,max…
quantile 分位数
count 非缺失值个数
idxmax,idxmin 最值索引
3.唯一值函数
unique 列的唯一值列表
nunique 列的唯一值个数
value_counts 列的值与频次
drop_duplicates 去重
4.替换函数
replace 通过字典或两个列表替换,参数ffill,bfill决定用之前(之后)最近非被替换值替换
where 符合条件保留
mask 符合条件去除
clip 两边咔嚓
5.排序函数
sort_values 根据列排序,可选定先后排的列
sort_index 根据索引排序,多级索引时可以选定先后排的索引

我用到的关于drop_duplicates的一个用法——求差集
pandas没有内置DF求差集的方法,但可以通过drop_duplicates实现
如下:

name = df['Name'].drop_duplicates()
name.value_counts()
Chengli Sun      1
Gaojuan Qin      1
Juan Qin         1
Qiang Zhou       1
Yanqiang Xu      1
                ..
Changquan Han    1
Gaoli Wu         1
Yanmei Qian      1
Xiaopeng Sun     1
Xiaofeng You     1
Name: Name, Length: 170, dtype: int64
del_name = pd.Series(['Yanli Zhang', 'Feng Yang', 'Yanfeng Han', 'Xiaofeng You'])
name = name.append(del_name).append(del_name).drop_duplicates(keep=False)
name.value_counts()
Chengli Sun      1
Peng You         1
Juan Qin         1
Yanqiang Xu      1
Feng Zhao        1
                ..
Chunqiang Chu    1
Changquan Han    1
Gaoli Wu         1
Yanmei Qian      1
Feng Zheng       1
Length: 166, dtype: int64

由上面结果看出,name的数量从170减少到166个,减掉了指定的四个
令所求集合C=A-B,本算法先求A+B+B,再使用drop_duplicates,并指定参数为keep=False保证重复的项被删除,单独在B中的项由于被加了两次所以会被删除,AB中都存在的显然也会被删除,故保留了A-B

s.clip(0, 2) # 前两个数分别表示上下截断边界
0    0.0000
1    1.2345
2    2.0000
3    0.0000
dtype: float64
s = pd.Series([-1, 1.2345, 100, -50])
s
0     -1.0000
1      1.2345
2    100.0000
3    -50.0000
dtype: float64

【练一练】

在 clip 中,超过边界的只能截断为边界值,如果要把超出边界的替换为自定义的值,应当如何做?

【我的思路】

假设自定义值为-999,可以用mask在两边各截一下

s.mask(s<0, -999).mask(s>2, -999)
0   -999.0000
1      1.2345
2   -999.0000
3   -999.0000
dtype: float64

【END】

四、窗口对象

这节相对于原教程没有多少新增加的内容,主要是做了习题,由于这一块掌握得还不好需要随时巩固所以保留了原文

pandas中有3类窗口,分别是滑动窗口rolling、扩张窗口expanding以及指数加权窗口ewm

1. 滑窗对象

要使用滑窗函数,就必须先要对一个序列使用.rolling得到滑窗对象,其最重要的参数为窗口大小window

s = pd.Series([1,2,3,4,5])
roller = s.rolling(window = 3)
cen_roller = s.rolling(window = 3, center=True)

试了下center=True,和预计的效果一样,会把当前数作为window的中心来处理,看一下效果:

roller.mean()
0    NaN
1    NaN
2    2.0
3    3.0
4    4.0
dtype: float64
cen_roller.sum()
0     NaN
1     6.0
2     9.0
3    12.0
4     NaN
dtype: float64

shift, diff, pct_change是一组类滑窗函数,它们的公共参数为periods=n,默认为1,分别表示取向前第n个元素的值、与向前第n个元素做差(与Numpy中不同,后者表示n阶差分)、与向前第n个元素相比计算增长率。这里的n可以为负,表示反方向的类似操作。

s = pd.Series([1,3,6,10,15])
s.shift(2)
0    NaN
1    NaN
2    1.0
3    3.0
4    6.0
dtype: float64
s.diff(3)
0     NaN
1     NaN
2     NaN
3     9.0
4    12.0
dtype: float64
s.pct_change()
0         NaN
1    2.000000
2    1.000000
3    0.666667
4    0.500000
dtype: float64
s.shift(-1)
0     3.0
1     6.0
2    10.0
3    15.0
4     NaN
dtype: float64
%%timeit
s.diff(2)
89.7 µs ± 4.06 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

将其视作类滑窗函数的原因是,它们的功能可以用窗口大小为n+1rolling方法等价代替:

%%timeit
s.rolling(3).apply(lambda x:list(x)[0]) # s.shift(2)
549 µs ± 77.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
 s.rolling(4).apply(lambda x:list(x)[-1]-list(x)[0]) # s.diff(3)
0     NaN
1     NaN
2     NaN
3     9.0
4    12.0
dtype: float64
def my_pct(x):
     L = list(x)
     return L[-1]/L[0]-1
s.rolling(2).apply(my_pct) # s.pct_change()
0         NaN
1    2.000000
2    1.000000
3    0.666667
4    0.500000
dtype: float64

【练一练】

rolling对象的默认窗口方向都是向前的,某些情况下用户需要向后的窗口,例如对1,2,3设定向后窗口为2的sum操作,结果为3,5,NaN,此时应该如何实现向后的滑窗操作?(提示:使用shift

【我的思路】

实在没想出来shift怎么做…但是感觉用倒序的方法好像挺容易想的,对逆序后的原序列按向前滑窗就相当于向后滑窗了,不过不知道这种方法速度会不会比shift慢,所以shift到底怎么做o.o…

a = pd.Series([1,2,3])
a[::-1].rolling(2).sum()[::-1]
0    3.0
1    5.0
2    NaN
dtype: float64

【END】

2. 扩张窗口

扩张窗口又称累计窗口,可以理解为一个动态长度的窗口,其窗口的大小就是从序列开始处到具体操作的对应位置,其使用的聚合函数会作用于这些逐步扩张的窗口上。具体地说,设序列为a1, a2, a3, a4,则其每个位置对应的窗口即[a1]、[a1, a2]、[a1, a2, a3]、[a1, a2, a3, a4]。

s = pd.Series([1, 6, 3, 10])
s.expanding().mean()
0    1.000000
1    3.500000
2    3.333333
3    5.000000
dtype: float64

【练一练】(我稍微改了下习题数据,更好纠错一点)

cummax, cumsum, cumprod函数是典型的类扩张窗口函数,请使用expanding对象依次实现它们。

【我的思路】

我先试了下最直观的解法,就是expanding之后加相对应的聚合函数,从结果上来看是对的,但是还有两个很讨厌的疑惑:

  • 我用expanding做出来的dtype都是float64,所以内置函数是怎么做到不改变数据类型的???【未解决】
  • 我觉得用我的方法做从原理上看感觉好像很慢,我测了一下也确实很慢,感觉应该是每扩张一步都要从头计算的原因,有没有只计算新进入元素的方法???【未解决】

今天太晚了,明天打算偷机看一下cumsum原码怎么做的,刚大致看了下,几个cum其实都调的一个函数,就改了个参数

%%timeit
s.cummax()
52.9 µs ± 2.81 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%%timeit
s.expanding().max()
386 µs ± 111 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
s.cumsum()
0     1
1     7
2    10
3    20
dtype: int64
s.expanding().sum()
0     1.0
1     7.0
2    10.0
3    20.0
dtype: float64
s.cumprod()
0      1
1      6
2     18
3    180
dtype: int64
s.expanding().apply(lambda x: np.prod(x))
0      1.0
1      6.0
2     18.0
3    180.0
dtype: float64

【END】

五、练习

Ex1:口袋妖怪数据集

现有一份口袋妖怪的数据集,下面进行一些背景说明:

  • #代表全国图鉴编号,不同行存在相同数字则表示为该妖怪的不同状态

  • 妖怪具有单属性和双属性两种,对于单属性的妖怪,Type 2为缺失值

  • Total, HP, Attack, Defense, Sp. Atk, Sp. Def, Speed分别代表种族值、体力、物攻、防御、特攻、特防、速度,其中种族值为后6项之和

  1. HP, Attack, Defense, Sp. Atk, Sp. Def, Speed进行加总,验证是否为Total值。

  2. 对于#重复的妖怪只保留第一条记录,解决以下问题:

  • 求第一属性的种类数量和前三多数量对应的种类
  • 求第一属性和第二属性的组合种类
  • 求尚未出现过的属性组合
  1. 按照下述要求,构造Series
  • 取出物攻,超过120的替换为high,不足50的替换为low,否则设为mid
  • 取出第一属性,分别用replaceapply替换所有字母为大写
  • 求每个妖怪六项能力的离差,即所有能力中偏离中位数最大的值,添加到df并从大到小排序
df = pd.read_csv('../data/pokemon.csv')
df.head(3)
# Name Type 1 Type 2 Total HP Attack Defense Sp. Atk Sp. Def Speed
0 1 Bulbasaur Grass Poison 318 45 49 49 65 65 45
1 2 Ivysaur Grass Poison 405 60 62 63 80 80 60
2 3 Venusaur Grass Poison 525 80 82 83 100 100 80
#1 对HP, Attack, Defense, Sp. Atk, Sp. Def, Speed进行加总,验证是否为Total值。
(df.drop(columns=['#', 'Name', 'Type 1', 'Type 2', 'Total']).sum(axis=1) == df['Total']).sum()
#统计了下各行的和,结果和total相等的求sum,值为800说明所有值相加确实都为total
800
#2
a = df.drop_duplicates(['#'])['Type 1'].value_counts()
print(f'type 1种类数量:{len(a)}\n 前三多种类:\n{a[:3]}')
b = df.drop_duplicates(['#']).drop_duplicates(['Type 1', 'Type 2'])['#'].count()
print(f'组合种类数: {b}')
types = list(df['Type 1'].append(df['Type 2'].dropna()).drop_duplicates().values)
import itertools
types = list(itertools.permutations(types, 2))
types = pd.DataFrame(types, columns=['Type 1', 'Type 2'])
exists = df.drop_duplicates(['#']).drop_duplicates(['Type 1', 'Type 2']).dropna()[['Type 1', 'Type 2']]
types.append(exists).append(exists).drop_duplicates(keep=False)
type 1种类数量:18
 前三多种类:
Water     105
Normal     93
Grass      66
Name: Type 1, dtype: int64
组合种类数: 143
Type 1 Type 2
0 Grass Fire
1 Grass Water
2 Grass Bug
3 Grass Normal
5 Grass Electric
... ... ...
300 Flying Rock
301 Flying Ghost
302 Flying Ice
304 Flying Dark
305 Flying Steel

181 rows × 2 columns

s = df['Attack']
res = s.mask(s>120, 'high').mask(s<50, 'low')
res = res.apply(lambda x:x if x=='low' or x=='high' else 'mid')
res.value_counts()
mid     579
low     133
high     88
Name: Attack, dtype: int64
#这道题没做出来,再思考一下
df['de'] = df.drop(columns=['#', 'Name', 'Type 1', 'Type 2', 'Total']).apply(lambda x:np.max((x-x.median()).abs()), 1)
df.sort_values(by='de', ascending=False).head()
# Name Type 1 Type 2 Total HP Attack Defense Sp. Atk Sp. Def Speed de
230 213 Shuckle Bug Rock 505 20 10 230 10 230 5 215.0
121 113 Chansey Normal NaN 450 250 5 5 35 105 50 207.5
261 242 Blissey Normal NaN 540 255 10 10 75 135 55 190.0
333 306 AggronMega Aggron Steel NaN 630 70 140 230 60 80 50 155.0
224 208 SteelixMega Steelix Steel Ground 610 75 125 230 55 95 30 145.0

后记

why named pandas? – Panel Data
起队名的时候本来也想起个熊猫相关的名字,然后查了一下pandas名字的由来,原来是来自panel data…很合理又很失望:(
不过并不妨碍我们继续喜欢pandas

你可能感兴趣的:(datawhale,pandas,python,数据分析,大数据,pandas)