【Task02】Pandas之基础

前言

本章的内容为Pandas基础

注,本次打卡所以用到的数据都放在了同级目录下的data文件夹中:

【Task02】Pandas之基础_第1张图片

一、文件的读取和写入

1.文件读取

#读csv 逗号分隔值
df_csv = pd.read_csv('data/my_csv.csv')
print(df_csv)
   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
#读txt
df_txt= pd.read_table('data/my_table.txt')
print(df_txt)
   col1 col2  col3             col4
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
#读excel
df_excel= pd.read_excel('data/my_excel.xlsx')
print(df_excel)
   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

安装2.0.1版本的xlrd会报错:
【Task02】Pandas之基础_第2张图片

原因是最近xlrd更新到了2.0.1版本,只支持.xls文件。所以pandas.read_excel(‘xxx.xlsx’)会报错。

解决办法:

pip install xlrd==1.2.0

1)header参数的用法:

#读txt
df_txt= pd.read_table('data/my_table.txt')
print(df_txt)
   col1 col2  col3             col4
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
# head参数使用 效果是在第一行填入新的索引
df_txt= pd.read_table('data/my_table.txt',header=None)
print(df_txt)
      0     1     2                3
0  col1  col2  col3             col4
1     2     a   1.4   apple 2020/1/1
2     3     b   3.4  banana 2020/1/2
3     6     c   2.5  orange 2020/1/5
4     5     d   3.2   lemon 2020/1/7

2)index_col参数用法

#读csv 逗号分隔值
df_csv = pd.read_csv('data/my_csv.csv')
print(df_csv)
   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
#读csv 逗号分隔值 使用index_col col3-5自动作为列索引?
df_csv = pd.read_csv('data/my_csv.csv',index_col=['col1','col2'])
print(df_csv)
           col3    col4      col5
col1 col2                        
2    a      1.4   apple  2020/1/1
3    b      3.4  banana  2020/1/2
6    c      2.5  orange  2020/1/5
5    d      3.2   lemon  2020/1/7

3)index_col参数用法

#读txt
df_txt= pd.read_table('data/my_table.txt')
print(df_txt)
   col1 col2  col3             col4
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
df_txt= pd.read_table('data/my_table.txt',usecols=['col1','col2'])
print(df_txt)
   col1 col2
0     2    a
1     3    b
2     6    c
3     5    d

4)parse_dates参数用法

#读csv 逗号分隔值
df_csv = pd.read_csv('data/my_csv.csv')
print(df_csv)
   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
#读csv 逗号分隔值
df_csv = pd.read_csv('data/my_csv.csv',parse_dates=['col5'])
print(df_csv)
   col1 col2  col3    col4       col5
0     2    a   1.4   apple 2020-01-01
1     3    b   3.4  banana 2020-01-02
2     6    c   2.5  orange 2020-01-05
3     5    d   3.2   lemon 2020-01-07

5)nrows参数用法

#读excel
df_excel= pd.read_excel('data/my_excel.xlsx')
print(df_excel)
   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
#读excel 设定行数
df_excel= pd.read_excel('data/my_excel.xlsx',nrows=3)
print(df_excel)
   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

6)sep参数用法

#直接读
df_txt = pd.read_table('data/my_table_special_sep.txt')
print(df_txt)
              col1 |||| col2
0  TS |||| This is an apple.
1    GQ |||| My name is Bob.
2         WT |||| Well done!
3    PT |||| May I help you?
#使用sep
df_txt = pd.read_table('data/my_table_special_sep.txt',sep='\|\|\|\|',engine='python')
print(df_txt)
  col1                 col2
0   TS    This is an apple.
1   GQ      My name is Bob.
2   WT           Well done!
3   PT      May I help you?

2.数据写入

刚才我们读出来的DataFrame格式的数据相当于一份原文件的copy,当我们修改后,需要将它与原文件进行同步,我们以.xlsx文件为例,了解一下数据写入过程:

例一、2.1 修改excel中的列名

先记录一下原始文档的数据:
【Task02】Pandas之基础_第3张图片

【Task02】Pandas之基础_第4张图片

使用pip install openpyxl解决,注意这里不需要import新的包:

【Task02】Pandas之基础_第5张图片

【Task02】Pandas之基础_第6张图片
原因出在文件打开被占用

我们关掉刚才打开的excel文件后继续:

#读excel函数 打印并返回excel中的df数据
>>> def getExcel(excel_path):
>>>     df_excel= pd.read_excel(excel_path)
>>>     return df_excel
>>> excel_path = 'data/my_excel.xlsx'
>>> df_excel = getExcel(excel_path)
>>> print(df_excel)
   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
#修改第一列列名
>>> df_excel.rename(columns={
     'col1':'hys'},inplace=True)
#df中数据
>>> print(df_excel)
   hys 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
#数据写入
>>> df_excel.to_excel(excel_path,index=False)
#查看原文件
>>> print(getExcel(excel_path))
   hys 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

再查看excel的数据:

【Task02】Pandas之基础_第7张图片

另说明,to_excel()方法中index的作用,如果设置成True(默认为True),会在df的基础上在最左侧加一列,比如:

【Task02】Pandas之基础_第8张图片

显然这不是我们想要的,所以需要在无实际需要的时候,最好把index设置成False。

例一、2.2 将txt文件保存为csv文件并指定分隔符

#读txt
>>> df_txt= pd.read_table('data/my_table.txt')
>>> print(df_txt)
   col1 col2  col3             col4
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
>>> df_txt.to_csv('data/my_txt_20201219.txt',sep=',',index=False)

我们采用的分隔符是逗号,注意这里sep只能设置成一个字符,否则会报错:

【Task02】Pandas之基础_第9张图片

查看文件:

【Task02】Pandas之基础_第10张图片

例一、2.3 表格数据转成markdown格式

#读csv
>>> df_csv = pd.read_csv('data/my_csv.csv')
>>> print(df_csv.to_markdown())

产生报错:
【Task02】Pandas之基础_第11张图片

通过pip install tabulate安装:

【Task02】Pandas之基础_第12张图片

再次运行:

#读csv
>>> df_csv = pd.read_csv('data/my_csv.csv')
>>> res = df_csv.to_markdown()
>>> print(type(df_csv))
<class 'pandas.core.frame.DataFrame'>
>>> print(type(res))
<class 'str'>
>>> print(res)
|    |   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 |

可以看到,生成的markdown格式数据类型是 str 类型

我们来一个套娃测试,因为我现在编辑博客的格式就是markdown,把上面的输出放在博客里试一下:

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

验证成功。

例一、2.4 表格数据转成latex格式

在data文件夹下新建一个包含‘a+b=c’的csv文件:

【Task02】Pandas之基础_第13张图片

#读csv
>>> df_csv = pd.read_csv('data/my_latex.csv')
>>> res = df_csv.to_latex()
>>> print(type(df_csv))
<class 'pandas.core.frame.DataFrame'>
>>> print(type(res))
<class 'str'>
>>> print(res)
\begin{
     tabular}{
     ll}
\toprule
Empty DataFrame
Columns: Index(['a+b = c'], dtype='object')
Index: Index([], dtype='object') \\
\bottomrule
\end{
     tabular}

TODO1:在Latex编辑器中验证。

二、基本数据结构

在上一章我们提到了,Pandas是基于Numpy构建的。Numpy有自己独特的ndarray数据类型,同样的,Pandas也有属于自己的两种基本的数据类型——SeriesDataFrame

下面让我们分别来介绍:

1.Series

class pandas.Series(data=None, index=None, dtype=None, name=None, copy=False, fastpath=False)

One-dimensional ndarray with axis labels

上面是官方文档对Series的介绍,翻译过来就是带有轴标签的一维ndarray

参数解释:

  • data:传入的数据
  • index:数据对应的标签
  • dtype:用作指定Series的输出数据类型,如果不指定会根据data去推断
  • name:Series的名字
  • copy:是否复制data
  • fastpath:是否快速精简模式

我们来举几个例子:

例二、1.1 创建Series数据

#定义data和index
>>> data = ['hys',25,'Beijing']
>>> index = ['name','age','loc']
#创建Series 方法一
>>> s = pd.Series(data,index,name='info')
#打印
print(s)
name        hys
age          25
loc     Beijing
Name: info, dtype: object
#创建Series 方法二
>>> s2 = pd.Series({
     'name':'hys','age': 25,'loc':'Beijing'},name='info')
>>> print(s2)
name        hys
age          25
loc     Beijing
Name: info, dtype: object

定义的s代表一个人的信息,其中包含3个标签(属性)。方法一是分别指定data和index来创建Series类型数据,而方法二是直接传入一个字典一并传入name和index。

Series常用属性:

属性 说明
values 获取数组
index 获取索引
name values的name
index.name 索引的name
dtype 数据类型
shape 数据的shape

Series类型数据的属性绝大部分都可以通过.属性名的方式来访问,下面举几个常用的属性:

例二、1.2 查看Series数据的属性

>>> s2 = pd.Series({
     'name':'hys','age': 25,'loc':'Beijing'},name='info')
>>> print(s2)
name        hys
age          25
loc     Beijing
Name: info, dtype: object

#访问values属性
>>> print(s2.values)
['hys' 25 'Beijing']

#访问index属性
>>> print(s2.index)
Index(['name', 'age', 'loc'], dtype='object')

#访问dtype属性
>>> print(s2.dtype)
object

#访问name属性
>>> print(s2.name)
info

#访问shape属性
>>> print(s2.shape)
(3,)

#通过index名来访问对应数据
>>> print(s2['name'])
hys

注意,在上面最后一个例子中我们使用了[]方式访问了Series数据指定index下的数据,它的调用方式类似于Python中的字典:

>>> infoDict = {
     'name':'hys','age': 25,'loc':'Beijing'}
>>> print(infoDict['loc'])
Beijing

与ndarray相似,Series数据类型中也有提前定义好的方法供我们使用,我们在这不进行单独讲解,会在本文第三节中一并讲解Series和DataFrame中常用的方法。

2.DataFrame

class pandas.DataFrame(data=None, index=None, columns=None, dtype=None, copy=False)

Two-dimensional, size-mutable, potentially heterogeneous tabular data.

官方对DataFrame的解释是,二维大小可变的潜在异构表格数据。

相比于Series,DataFrame中的参数少了一个name和fastpath,多了一个columns,columns是列标签的意思。

举例说明:

例二、2.1 创建DataFrame数据

>>> data = [['hys',25,'Beijing'],['xmy',25,'Shanghai']]
>>> index = ['01','02']
>>> columns = ['name','age','loc']
#创建DataFrame 方法一
>>> df = pd.DataFrame(data,index,columns)
>>> print(df)
   name  age       loc
01  hys   25   Beijing
02  xmy   25  Shanghai
#创建DataFrame 方法二 
>>> dataDict = {
     'name':['hys','xmy'],'age':[25,25],'loc':['Beijing','Shanghai']}
>>> df2 = pd.DataFrame(dataDict)
>>> print(df2)
  name  age       loc
0  hys   25   Beijing
1  xmy   25  Shanghai

同样地,我们也可以利用两种方式创建DataFrame型数据,上面的方法一仍然是常规方法,index和columns分别代表视觉上的行索引和列索引。方法二依然采用字典方式构建,其中字典的键名代表columns,index默认为0,1,…直到任意键值列表的长度-1。

同样地,我们也可以利用.属性名、[行索引名]、[列索引名]去访问指定的属性:

例二、2.2 查看DataFrame数据的属性

>>> dataDict = {
     'name':['hys','xmy'],'age':[25,25],'loc':['Beijing','Shanghai']}
>>> df2 = pd.DataFrame(dataDict)
>>> print(df2)
  name  age       loc
0  hys   25   Beijing
1  xmy   25  Shanghai

#访问values属性
>>> print(df2.values)
[['hys' 25 'Beijing']
 ['xmy' 25 'Shanghai']]

#访问index属性
>>> print(df2.index)
RangeIndex(start=0, stop=2, step=1)

#访问culumns属性
>>> print(df2.columns)
Index(['name', 'age', 'loc'], dtype='object')

#访问dtypes属性
>>> print(df2.dtypes)
name    object
age      int64
loc     object
dtype: object

#访问shape属性
>>> print(df2.shape)
(2, 3)

#通过columns名来访问对应数据
>>> print(df2['name'],type(df2['name']))
0    hys
1    xmy
Name: name, dtype: object <class 'pandas.core.series.Series'>

这里要注意DataFrame数据是没有dtype属性的,取而代之的是dtypes属性,它返回的是每一行单独的dtype和整个DataFrame整体的dtype。

另外值得注意的一点是,型如df2['name']的列访问得到的数据类型是Series.

三、常用基本函数

由于从某种程度上可以把Series数据当做DataFrame数据的子集,所以在本节我们以DataFrame为主介绍pandas数据类型的常用基本函数。

采用的数据集来自data文件夹下的learn_pandas.csv:

【Task02】Pandas之基础_第14张图片
利用上一节的知识简单查看一下数据集的重要属性:

#读入数据
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
#查看数据的shape
>>> print(df_data.shape)
(200, 10)

#查看列标签
>>> print(df_data.columns)
Index(['School', 'Grade', 'Name', 'Gender', 'Height', 'Weight', 'Transfer',
       'Test_Number', 'Test_Date', 'Time_Record'],
      dtype='object')

可以看到,所用数据集的shape为(200,10),共10个列标签,且没有设置行标签。

1.汇总函数

DataFrame.head(n=5)
DataFrame.tail(n=5)

head()方法tail()方法分别返回前n行和后n行的数据,默认n=5:

例三、1.1 按行返回DataFrame数据

>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[df_data.columns[:7]]
>>> print(df_data.head())
>>> print(df_data.tail())

【Task02】Pandas之基础_第15张图片

例三、1.2 返回DataFrame数据信息概括和列统计量

>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[df_data.columns[:7]]
>>> print(df_data.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 200 entries, 0 to 199
Data columns (total 7 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   School    200 non-null    object 
 1   Grade     200 non-null    object 
 2   Name      200 non-null    object 
 3   Gender    200 non-null    object 
 4   Height    183 non-null    float64
 5   Weight    189 non-null    float64
 6   Transfer  188 non-null    object 
dtypes: float64(2), object(5)
memory usage: 11.1+ KB
None
>>> print(df_data.describe())
           Height      Weight
count  183.000000  189.000000
mean   163.218033   55.015873
std      8.608879   12.824294
min    145.400000   34.000000
25%    157.150000   46.000000
50%    161.900000   51.000000
75%    167.500000   65.000000
max    193.900000   89.000000

2.特征统计函数

如果不想返回describe()方法返回的全部统计量,我们可以使用单独的统计方法进行计算,默认为按列统计(axis=0),也可以指定axis的值:

>>> df_data = pd.read_csv('./data/learn_pandas.csv')
#选取身高和体重列
>>> df_data = df_data[['Height','Weight']]
#统计非缺失值个数
>>> print(df_data.count())
Height    183
Weight    189
dtype: int64

#计算均值
>>> print(df_data.mean())
Height    163.218033
Weight     55.015873
dtype: float64

#计算标准差
>>> print(df_data.std())
Height     8.608879
Weight    12.824294
dtype: float64

#最小值
>>> print(df_data.min())
Height    145.4
Weight     34.0
dtype: float64

#最大值
>>> print(df_data.max())
Height    193.9
Weight     89.0
dtype: float64

#四分位数 25%和75%百分位
>>> print(df_data.quantile(0.25))
Height    157.15
Weight     46.00
Name: 0.25, dtype: float64
>>> print(df_data.quantile(0.75))
Height    167.5
Weight     65.0
Name: 0.75, dtype: float64

#返回最大最小值的行索引
>>> print(df_data.idxmax())
Height    193
Weight      2
dtype: int64
>>> print(df_data.idxmin())
Height    143
Weight     49
dtype: int64

3.唯一值函数

unique()方法可以获得由唯一值组成的数组,nunique()方法返回的是唯一值的个数:

>>> df_data_school = pd.read_csv('./data/learn_pandas.csv')['School']
>>> print(df_data_school.unique())
['Shanghai Jiao Tong University' 'Peking University' 'Fudan University'
 'Tsinghua University']
>>> print(df_data_school.nunique())
4
>>> print(len(df_data_school.unique()))
4

不难看出,x.nunique()len(x.unique())是等价的。

value_counts()方法可以查看每个唯一值对应出现的次数:

>>> print(df_data_school.value_counts())
Tsinghua University              69
Shanghai Jiao Tong University    57
Fudan University                 40
Peking University                34
Name: School, dtype: int64
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Gender','Transfer','Name']]
>>> print(df_data.drop_duplicates(['Gender', 'Transfer']))
    Gender Transfer            Name
0   Female        N    Gaopeng Yang
1     Male        N  Changqiang You
12  Female      NaN        Peng You
21    Male      NaN   Xiaopeng Shen
36    Male        Y    Xiaojuan Qin
43  Female        Y      Gaoli Feng

默认drop_duplicates()方法参数keep为first,代表展示第一次出现的行,值为last表示展示最后一次出现的行。

易知这两种情况返回的行数为每列属性种类的累积

duplicated()方法返回的是对应列的值是否重复,类型为布尔类型:

>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Gender','Transfer','Name']]
>>> print(df_data.duplicated(['Gender', 'Transfer']).head(6))
0    False
1    False
2     True
3     True
4     True
5     True
dtype: bool

返回结果代表的是当前行的’Gender’和’Transfer’的组合是否从未出现过。

4.替换函数

1)映射替换

replace()方法可以用于映射替换:

>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> replace_dict = {
     'Male':'男','Female':'女'}
>>> print(df_data['Gender'].replace(replace_dict).head(6))
012345    女
Name: Gender, dtype: object

我们还可以通过锁定替换的值后设定replace的method参数指定替换对齐方式,‘ffill’代表‘forward’向前,‘bfill’代表backward向后:

>>> data = pd.Series(np.random.randint(0,3,5))
>>> print(data)
0    0
1    2
2    0
3    2
4    1
dtype: int32
>>> print(data.replace([1],method='ffill'))
0    0
1    2
2    0
3    2
4    2
dtype: int32
>>> print(data.replace([1],method='bfill'))
0    0
1    2
2    0
3    2
4    1
dtype: int32

我们可以看到,当被替换值处于边界状态下,后面无值可替,它会保持不变。

2)逻辑替换

where()方法和mask()方法的作用正好是相反的,前者替换不满足条件的值,后者替换满足条件的值,默认将被替换值变为NaN类型:

>>> data = pd.Series(np.random.randint(0,3,5))
>>> print(data)
0    0
1    1
2    0
3    0
4    0
dtype: int32
>>> print(data .where(data<1))
0    0.0
1    NaN
2    0.0
3    0.0
4    0.0
dtype: float64
>>> print(data .mask(data<1))
0    NaN
1    1.0
2    NaN
3    NaN
4    NaN
dtype: float64

观察到一个小小的细节,就是这两种方法都使原始数据类型从int32变为了float64。

3)数值替换

这里介绍3个方法:round()abs()clip(),分别代表取整、求绝对值和截断:

>>> data = pd.Series([1.5,-1.6,1.4,-1.3])
>>> print(data)
0    1.5
1   -1.6
2    1.4
3   -1.3
dtype: float64
>>> print(data.round(0))
0    2.0
1   -2.0
2    1.0
3   -1.0
dtype: float64
>>> print(data.abs())
0    1.5
1    1.6
2    1.4
3    1.3
dtype: float64
>>> print(data.clip(-1,1))
0    1.0
1   -1.0
2    1.0
3   -1.0
dtype: float64

我们可以注意到,round()方法采用的是四舍五入原则。

练一练

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

超出边界的值替换成指定的值:

思路:利用where进行多条件判断,并指定替换值

>>> data = pd.Series([1.5,-1.6,1.4,-1.3])
>>> print(data)
>>> print(data.where((data>-1) & (data <2),100))
0    1.5
1   -1.6
2    1.4
3   -1.3
dtype: float64
0      1.5
1    100.0
2      1.4
3    100.0
dtype: float64

5.排序函数

1)值排序

>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Grade', 'Name', 'Height', 'Weight']].set_index(['Grade','Name'])
#默认为True 代表升序排列
>>> print(df_data.sort_values('Height').head())
                         Height  Weight
Grade     Name                         
Junior    Xiaoli Chu      145.4    34.0
Senior    Gaomei Lv       147.3    34.0
Sophomore Peng Han        147.8    34.0
Senior    Changli Lv      148.7    41.0
Sophomore Changjuan You   150.5    40.0

#False代表升序排列
>>> print(df_data.sort_values('Height',ascending=False).head())
                        Height  Weight
Grade    Name                         
Senior   Xiaoqiang Qin   193.9    79.0
         Mei Sun         188.9    89.0
         Gaoli Zhao      186.5    83.0
Freshman Qiang Han       185.3    87.0
Senior   Qiang Zheng     183.9    87.0

如果指定了两个列,那么在身高相同时,默认两列均使用升序排列。

2)索引排序

索引排序需要在列名前指定level,否则会报错:

在这里插入图片描述

>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Grade', 'Name', 'Height', 'Weight']].set_index(['Grade','Name'])
#默认为True 代表升序排列
>>> print(df_data.sort_index(level=['Grade','Name'],ascending=[True,False]).head())
                        Height  Weight
Grade    Name                         
Freshman Yanquan Wang    163.5    55.0
         Yanqiang Xu     152.4    38.0
         Yanqiang Feng   162.3    51.0
         Yanpeng Lv        NaN    65.0
         Yanli Zhang     165.1    52.0

这里的升降序指得是字典顺序。

6.apply方法

apply()用于用指定的自定义函数去计算该数据,如:

>>> def func(x):
>>>     return x.max()
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Height','Weight']].head()
>>> print(df_data.apply(func))
Height    188.9
Weight     89.0
dtype: float64

当然此时可以利用拉姆达函数去省去def,但效果不如直接使用内置函数:

【Task02】Pandas之基础_第16张图片

四、窗口对象

1.滑窗对象

rolling()方法为例举例说明:

>>> data = pd.Series(np.arange(10))
>>> roller = data.rolling(window=4)
>>> roller.mean()
0    NaN
1    NaN
2    NaN
3    1.5
4    2.5
5    3.5
6    4.5
7    5.5
8    6.5
9    7.5
dtype: float64

窗口默认为窗口的最后一个单元先放到数据的第一个位置,然后一直滑动到最后一个位置所组成的窗口位置集合,长度等于数据的长度,值为NaN的行有数据长度-1个。

滑动窗口支持传入自定义函数,如:

>>> data = pd.Series(np.arange(10))
>>> roller = data.rolling(window=4)
>>> print(roller.apply(lambda x:x.min()))
0    NaN
1    NaN
2    NaN
3    0.0
4    1.0
5    2.0
6    3.0
7    4.0
8    5.0
9    6.0
dtype: float64

shift()方法表示将数据按指定方向移动,比如:

>>> data = pd.Series(np.arange(10))
>>> print(data)
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int32
>>> data.shift(5)
0    NaN
1    NaN
2    NaN
3    NaN
4    NaN
5    0.0
6    1.0
7    2.0
8    3.0
9    4.0
dtype: float64

即表示数据向下移动5个单位,空缺用NaN补全,并且转换了dtype。如果实参为负数,则表示向下移动,移动距离为负数的绝对值。

diff()方法表示数据的差值,如:

>>> data = pd.Series(np.arange(10))
>>> print(data)
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int32
>>> print(data.diff(2))
0    NaN
1    NaN
2    2.0
3    2.0
4    2.0
5    2.0
6    2.0
7    2.0
8    2.0
9    2.0
dtype: float64

上面的例子表示,每个数据减去其上面2个位置的数据得到的如果,如果值不存在则填NaN。注意,如果不指明位置,默认为1,即上面相邻的数据。如果实参为负数,同上。

pct_change()方法表示计算数据的相对增长率,如不输入实参,则默认将相邻的上方数据作为参考指标进行计算:

>>> data = pd.Series(np.arange(10))
>>> print(data)
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int32
>>> print(data.pct_change())
0         NaN
1         inf
2    1.000000
3    0.500000
4    0.333333
5    0.250000
6    0.200000
7    0.166667
8    0.142857
9    0.125000
dtype: float64

练一练

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

2.扩张窗口

>>> data = pd.Series(np.arange(10))
>>> print(data.expanding().mean())
0    0.0
1    0.5
2    1.0
3    1.5
4    2.0
5    2.5
6    3.0
7    3.5
8    4.0
9    4.5
dtype: float64

扩张窗口等价与把滑动窗口值为Nan的位置补全为0,相当于在左侧延长n-1长度的0(n为数据长度)

练一练

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

>>> s = pd.Series([1,3,6,10])
#cummax
>>> print(s.expanding().max())
0     1.0
1     3.0
2     6.0
3    10.0
dtype: float64
#cumsum
>>> print(s.expanding().sum())
0     1.0
1     4.0
2    10.0
3    20.0
dtype: float64
#cumprod
>>> print(s.expanding().apply(lambda x:x.prod()))
0      1.0
1      3.0
2     18.0
3    180.0
dtype: float64

前两个实现起来非常简单,最后一个值得思考,因为下面这种方式是不行的,只好用apply()方法:

【Task02】Pandas之基础_第17张图片

五、练习

Ex1:口袋妖怪数据集

>>> data = pd.read_csv('data/pokemon.csv')
>>> data.head()

【Task02】Pandas之基础_第18张图片

1.验证Total值是否准确

>>> data = pd.read_csv('data/pokemon.csv')
>>> total = data['Total']
>>> data_six = data[data.columns[5:]]
>>> total_calc = data_six.sum(axis=1)
>>> print(np.all(total==total_calc))
True

思路:先把Total和其余6个属性值分堆处理,存放成Series数据,最后利用Numpy的all()方法进行比较。

2.“#”属性重复的妖怪只保留第一条记录

>>> data = pd.read_csv('data/pokemon.csv')
>>> data = data.drop_duplicates('#',keep='first')
>>> print(data.shape)
(721, 11)

先删掉#重复的妖怪。

1)求第一属性的种类数量和前三多数量对应的种类

>>> Type1 = data['Type 1']
>>> print(Type1.nunique())
18
>>> print(Type1.value_counts()[:3].index.values)
['Water' 'Normal' 'Grass']

思路:利用nunique()方法求出第一属性种类数量,利用value_counts()方法求出数量降序排列的第一属性种类名,然后通过索引和index()方法取得Index对象,最终通过.values属性拿到List类型的数据。

2)求第一属性和第二属性的组合种类

>>> res = data.drop_duplicates(['Type 1','Type 2'])
>>> print(res.shape)
(143, 11)

思路:利用drop_duplicates()方法求出包含第一和第二属性无重复的组合种类。

3)求尚未出现过的属性组合

#返回指定列名的无重复数据列表
#ndarray格式 求得所有类别 
>>> nd_types = data['Type 1'].unique()
>>> print(nd_types.shape)
(18,)
#已知总共18个属性 共18*18=324个组合种类 名称存放在名为total_types的list中
>>> total_types = [i+'_'+j if i!=j else i for i in nd_types for j in nd_types]
#获取现有的组合种类数据
>>> s_nowTypes = data.drop_duplicates(['Type 1','Type 2'])
>>> print(s_nowTypes.shape)
(143, 11)
>>> def func(x):
>>>     if type(x[3])==str:
>>>         return x[2]+'_'+x[3]
>>>     else:
>>>         return x[2]
#利用func函数,过滤无第二属性的妖怪,并存入now_types的Series数据类型中
>>> now_types = s_nowTypes.apply(func,axis=1)
#列表推导表达式获得结果
>>> res = [x for x in total_types if x not in now_types.values]
>>> print(len(res))
181

思路:
1)先求出所有属性,共18种,然后组合种类得到324种,注意两种一样的属性不要设成“x_x”,而直接叫x,将全部种类名存入名为total_types的List中。
2)然后根据上一题,找到当前已有的种类,此处利用apply()方法并将axis设为1即为按行遍历,注意此时data的列长仍为11,所以需要判断第4列的type2是否为NaN,等价于判断否是为字符,如果是字符的话说明是双属性妖怪,返回拼接后的结果,否则只返回type1。将生成的数据存到名为now_types的Series中。
3)利用列表推导表达式判断尚未出现过的属性组合。

另外,分析一下对怪物数据的理解,首先假设data代表全部800条数据,其中存在重复‘#’值的情况,这代表不同怪物的不同形态,比如:

【Task02】Pandas之基础_第19张图片
代欧奇希斯,拥有四个战斗形态分别是,一般形态、攻击形态、防御形态与速度形态,根据形态不同它的各项指标会大幅度变化。

我们通过data1 = data.drop_duplicates('#',keep='first')只取同一个怪物的不同形态的第一种,data1中含有731条数据。然后通过data2 = data.drop_duplicates(['Type 1','Type 2'])只取相同属性组合的第一种,data2中含有143条数据,代表目前存在的属性组合。因为怪物不存在两个属性均为一个的情况,所以单属性的怪物可以被双属性相同的怪物代表,即属性组合一共18*18=324种,减去data2的数据量,即324-143=181种。

更进一步,我们可以做出属性组合矩阵的可视化呈现:

#组合种类矩阵 行索引表示第一属性 列索引表示第二属性
>>> df_types = pd.DataFrame(index=types,columns=types)
#单次增加不同种类怪物数量
>>> def add(index1,index2):
>>>     value = df_types[index1][index2]
    #如果当前统计值为NaN
>>>     if type(value)==float:
>>>         df_types[index1][index2] = 1
>>>     else:
>>>         df_types[index1][index2] += 1
>>> def func(x):
    #这里调换了i,j的顺序,为了适应df先列后行的访问次序 如df_types['Grass']['Fire']=0 访问的是'Grass'列'Fire'行
    #i为type2属性
>>>     i = x[3]
    #j为type1属性
>>>     j = x[2]
    #如果type2为nan
>>>     if type(i)==float:
        #则将type2赋值为type1
>>>         i = j
    #调用函数
>>>     add(i,j)
>>> data = pd.read_csv('data/pokemon.csv')
>>> data = data.drop_duplicates('#',keep='first')
>>> data.apply(func,axis=1)
>>> print(df_types.count().sum())
143
>>> df_types

【Task02】Pandas之基础_第20张图片
df_types存放的是不同属性组合,有了这个DataFrame数据,我们就可以轻易获得有关组合属性的相关数据,比如获得单属性怪物的数量:

#求矩阵的迹 实际意义为单属性怪物的数量
>>> df_types.values.trace()
371

3.按要求构造Series

a.生成包含物攻的Series并按要求替换

>>> data = pd.read_csv('data/pokemon.csv')
>>> data_attack = data['Attack']
>>> data_attack.mask((data_attack>=50) & (data_attack <=120),100,inplace=True)
>>> data_attack.mask((data_attack>120),150,inplace=True)
>>> data_attack.mask((data_attack<50),0,inplace=True)
>>> data_attack.replace({
     0:'low',100:'mid',150:'high'},inplace=True)
>>> print(data_attack)
0       low
1       mid
2       mid
3       mid
4       mid
       ... 
795     mid
796    high
797     mid
798    high
799     mid
Name: Attack, Length: 800, dtype: object
>>> print(data_attack.unique())
['low' 'mid' 'high']

思路:第一步用mask()方法做一个等价替换,把三个区域的值都用一个数字表示;第二部用replace()方法的映射替换。

如果省去第二部直接替换,在执行第二个mask()的时候,会出现字符(mid)和数字进行比较,无法顺利实现,所以采用分阶段的方式。

b.生成包含第一属性的Series并转为大写字母

>>> data = pd.read_csv('data/pokemon.csv')
#方法一 直接转换
>>> data_type1 = data['Type 1']
>>> data_type1 = data['Type 1'].str.upper()
>>> print(data_type1)
#方法二 使用apply转换
>>> data_type1 = data['Type 1']
>>> data_type1 = data_type1.apply(lambda x:str.upper(x))
>>> print(data_type1)
#方法三 使用replace转换
>>> data_type1 = data['Type 1']
>>> replace_Dict = {
     x : str.upper(x) for x in data_type1.unique() }
>>> data_type1.replace(replace_Dict,inplace=True)
>>> print(data_type1)
0        GRASS
1        GRASS
2        GRASS
3        GRASS
4         FIRE
        ...   
795       ROCK
796       ROCK
797    PSYCHIC
798    PSYCHIC
799       FIRE
Name: Type 1, Length: 800, dtype: object

c.生成每个妖怪的离差并从大到小排序

#作用求离差
>>> def getDev(x):
>>>     return ((x-x.median()).abs()).max()
>>> data = pd.read_csv('data/pokemon.csv')
#data表示所有能力值
>>> data = data[data.columns[-6:]]
>>> res = data.apply(getDev,axis=1).sort_values(ascending=False)
>>> print(res)
261    190.0
121    185.0
224    160.0
230    160.0
333    160.0
       ...  
175    -25.0
732    -27.0
446    -28.0
255    -30.0
206    -35.0
Length: 800, dtype: float64

思路:对整个DataFrame使用apply()方法,其中自定义函数getDev()的作用是返回离差,然后对离差进行降序排列(ascending设为False)。

仔细解释一下getDev()函数,首先在调用函数时,指定axis为1,即按行运算,然后x代表着每行的数据,属于Series类型。因为Series依然可以进行向量化运算,所以x-x.median()生成的是六项能力与中位数的差值的Series类型数据,然后利用max()方法求得这个妖怪的离差并返回。

Ex2:指数加权窗口

1.expanding窗口实现ewm

#自定义方法
>>> def my_ewm(s,alpha):
>>>     def func(x):
>>>         s_deno = (1-alpha)**np.arange(x.size-1,-1,-1)
>>>         return (s_deno*x).sum()/s_deno.sum()
>>>     return s.expanding().apply(func)
>>> np.random.seed(20201219)
>>> s  = pd.Series(np.random.randint(-1,2,30).cumsum())
>>> alpha = 0.2
#系统方法
>>> print(s.ewm(alpha=alpha).mean().head())
0    0.000000
1    0.555556
2    1.147541
3    1.436314
4    1.603998
dtype: float64
#自定义方法
>>> print(my_ewm(s,alpha).head())
0    0.000000
1    0.555556
2    1.147541
3    1.436314
4    1.603998
dtype: float64

思路:从expanding()方法入手,对于每一个扩张窗口,第一个窗口值的(1-α)的次数为0,然后依次递增,且数目和窗口宽度相同,反过来说,由加权归一化公式可知窗口的第一个值是与最大幂数的(1-α)相乘,因此考虑先建立一个ndarray数组用于存储倒序的幂数数组,首项为窗口大小-1,长度为窗口大小。然后利用Numpy的向量化性质进行公式复现即可。

注意np.arange(10,-1,-1)生成的是以10开始,0结束的递减ndarray数组。

2.给定限制窗口n,给出公式及实现

新的权重没有发生变化,但是 y t y{_t} yt的公式中求和公式的上限变为n-1:

在这里插入图片描述

区别在于限制了窗口大小,所以我们在上一题设计的幂数数组的长度也会固定,实现方法为:

#自定义方法
>>> def my_new_ewm(s,alpha,n):
>>>     def func(x):
>>>         return (s_deno*x).sum()/s_deno.sum()
>>>		s_deno = (1-alpha)**np.arange(n-1,-1,-1)
>>>     return s.rolling(window=n).apply(func)
>>> np.random.seed(20201219)
>>> s  = pd.Series(np.random.randint(-1,2,30).cumsum())
>>> alpha = 0.2
>>> n=4
#自定义方法
>>> print(my_new_ewm(s,alpha,n))
0         NaN
1         NaN
2         NaN
3    1.436314
4    1.826558
dtype: float64

实现方法的改动在于函数参数新增了一个窗口宽度n,另外由于窗口保持固定,所以s_deno数组只需生成一次即可,自定义函数func()没有本质的变化。

参考文献

#pandas DataFrame的修改方法
1.https://www.cnblogs.com/datasnail/p/9787808.html

#pandas官方API
2.https://pandas.pydata.org/pandas-docs/stable

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