74.Pandas的应用-5.md 13.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
## Pandas的应用-5

### DataFrame的应用

#### 窗口计算

`DataFrame`对象的`rolling`方法允许我们将数据置于窗口中,然后就可以使用函数对窗口中的数据进行运算和处理。例如,我们获取了某只股票近期的数据,想制作5日均线和10日均线,那么就需要先设置窗口再进行运算。我们可以使用三方库`pandas-datareader`来获取指定的股票在某个时间段内的数据,具体的操作如下所示。

安装`pandas-datareader`三方库。

```Bash
pip install pandas-datareader
```

通过`pandas-datareader` 提供的`get_data_stooq`从 Stooq 网站获取百度(股票代码:BIDU)近期股票数据。

```Python
import pandas_datareader as pdr

baidu_df = pdr.get_data_stooq('BIDU', start='2021-11-22', end='2021-12-7')
baidu_df.sort_index(inplace=True)
baidu_df
```

输出:

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
27
<img src="https://github.com/jackfrued/mypic/raw/master/20211208205710.png" style="zoom:38%;">
28 29 30 31 32 33 34 35 36

上面的`DataFrame``Open``High``Low``Close``Volume`五个列,分别代码股票的开盘价、最高价、最低价、收盘价和成交量,接下来我们对百度的股票数据进行窗口计算。

```Python
baidu_df.rolling(5).mean()
```

输出:

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
37
<img src="https://github.com/jackfrued/mypic/raw/master/20211208205932.png" style="zoom:38%;">
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

上面的`Close` 列的数据就是我们需要的5日均线,当然,我们也可以用下面的方法,直接在`Close`列对应的`Series`对象上计算5日均线。

```Python
baidu_df.Close.rolling(5).mean()
```

输出:

```
Date
2021-11-22        NaN
2021-11-23        NaN
2021-11-24        NaN
2021-11-26        NaN
2021-11-29    150.608
2021-11-30    151.014
2021-12-01    150.682
2021-12-02    150.196
2021-12-03    147.062
2021-12-06    146.534
2021-12-07    146.544
Name: Close, dtype: float64
```

#### 相关性判定

在统计学中,我们通常使用协方差(covariance)来衡量两个随机变量的联合变化程度。如果变量 $X$ 的较大值主要与另一个变量 $Y$ 的较大值相对应,而两者较小值也相对应,那么两个变量倾向于表现出相似的行为,协方差为正。如果一个变量的较大值主要对应于另一个变量的较小值,则两个变量倾向于表现出相反的行为,协方差为负。简单的说,协方差的正负号显示着两个变量的相关性。方差是协方差的一种特殊情况,即变量与自身的协方差。

$$
cov(X,Y) = E((X - \mu)(Y - \upsilon)) = E(X \cdot Y) - \mu\upsilon
$$

如果 $X$ 和 $Y$ 是统计独立的,那么二者的协方差为0,这是因为在 $X$ 和 $Y$ 独立的情况下:

$$
E(X \cdot Y) = E(X) \cdot E(Y) = \mu\upsilon
$$

协方差的数值大小取决于变量的大小,通常是不容易解释的,但是正态形式的协方差大小可以显示两变量线性关系的强弱。在统计学中,皮尔逊积矩相关系数就是正态形式的协方差,它用于度量两个变量 $X$ 和 $Y$ 之间的相关程度(线性相关),其值介于`-1``1`之间。

$$
\rho{X,Y} = \frac {cov(X, Y)} {\sigma_{X}\sigma_{Y}}
$$

估算样本的协方差和标准差,可以得到样本皮尔逊系数,通常用希腊字母 $\rho$ 表示。

$$
\rho = \frac {\sum_{i=1}^{n}(X_i - \bar{X})(Y_i - \bar{Y})} {\sqrt{\sum_{i=1}^{n}(X_i - \bar{X})^2} \sqrt{\sum_{i=1}^{n}(Y_i - \bar{Y})^2}}
$$

我们用 $\rho$ 值判断指标的相关性时遵循以下两个步骤。

1. 判断指标间是正相关、负相关,还是不相关。
    - 当 $ \rho \gt 0 $,认为变量之间是正相关,也就是两者的趋势一致。
    - 当 $ \rho \lt 0 $,认为变量之间是负相关,也就是两者的趋势相反。
    - 当 $ \rho = 0 $,认为变量之间是不相关的,但并不代表两个指标是统计独立的。
2. 判断指标间的相关程度。
    - 当 $ \rho $ 的绝对值在 $ [0.6,1] $ 之间,认为变量之间是强相关的。
    - 当 $ \rho $ 的绝对值在 $ [0.1,0.6) $ 之间,认为变量之间是弱相关的。
    - 当 $ \rho $ 的绝对值在 $ [0,0.1) $ 之间,认为变量之间没有相关性。

皮尔逊相关系数适用于:

 1. 两个变量之间是线性关系,都是连续数据。
 2. 两个变量的总体是正态分布,或接近正态的单峰分布。
 3. 两个变量的观测值是成对的,每对观测值之间相互独立。

`DataFrame`对象的`cov`方法和`corr`方法分别用于计算协方差和相关系数,`corr`方法的第一个参数`method`的默认值是`pearson`,表示计算皮尔逊相关系数;除此之外,还可以指定`kendall`或`spearman`来获得肯德尔系数或斯皮尔曼等级相关系数。

接下来,我们从名为`boston_house_price.csv`的文件中获取著名的[波士顿房价数据集](https://www.heywhale.com/mw/dataset/590bd595812ede32b73f55f2)来创建一个`DataFrame`,我们通过`corr`方法计算可能影响房价的`13`个因素中,哪些跟房价是正相关或负相关的,代码如下所示。

```Python
boston_df = pd.read_csv('data/csv/boston_house_price.csv')
boston_df.corr()
```

> **说明**:如果需要上面例子中的 CSV 文件,可以通过下面的百度云盘地址进行获取,数据在《从零开始学数据分析》目录中。链接:<https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g>,提取码:e7b4。

输出:

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
119
<img src="https://github.com/jackfrued/mypic/raw/master/20211208213325.png">
120 121 122 123 124 125 126 127 128

斯皮尔曼相关系数对数据条件的要求没有皮尔逊相关系数严格,只要两个变量的观测值是成对的等级评定资料,或者是由连续变量观测资料转化得到的等级资料,不论两个变量的总体分布形态、样本容量的大小如何,都可以用斯皮尔曼等级相关系数来进行研究。我们通过下面的方式来计算斯皮尔曼相关系数。

```Python
boston_df.corr('spearman')
```

 输出:

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
129
<img src="https://github.com/jackfrued/mypic/raw/master/20211208213518.png">
130 131 132 133 134 135 136

在 Notebook 或 JupyterLab 中,我们可以为`PRICE`列添加渐变色,用颜色直观的展示出跟房价负相关、正相关、不相关的列,`DataFrame`对象`style`属性的`background_gradient`方法可以完成这个操作,代码如下所示。

```Python
boston_df.corr('spearman').style.background_gradient('RdYlBu', subset=['PRICE'])
```

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
137
<img src="https://github.com/jackfrued/mypic/raw/master/20211208215228.png">
138 139 140 141 142 143 144

上面代码中的`RdYlBu`代表的颜色如下所示,相关系数的数据值越接近`1`,颜色越接近红色;数据值越接近`1`,颜色越接近蓝色;数据值在`0`附件则是黄色。

```Python
plt.get_cmap('RdYlBu')
```

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
145
<img src="https://github.com/jackfrued/mypic/raw/master/20211208215057.png">
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362

### Index的应用

我们再来看看`Index`类型,它为`Series``DataFrame`对象提供了索引服务,常用的`Index`有以下几种。

#### 范围索引(RangeIndex)

代码:

```Python
sales_data = np.random.randint(400, 1000, 12)
month_index = pd.RangeIndex(1, 13, name='月份')
ser = pd.Series(data=sales_data, index=month_index)
ser
```

输出:

```
月份
1     703
2     705
3     557
4     943
5     961
6     615
7     788
8     985
9     921
10    951
11    874
12    609
dtype: int64
```

#### 分类索引(CategoricalIndex)

代码:

```Python
cate_index = pd.CategoricalIndex(
    ['苹果', '香蕉', '苹果', '苹果', '桃子', '香蕉'],
    ordered=True,
    categories=['苹果', '香蕉', '桃子']
)
ser = pd.Series(data=amount, index=cate_index)
ser
```

输出:

```
苹果    6
香蕉    6
苹果    7
苹果    6
桃子    8
香蕉    6
dtype: int64
```

代码:

```Python
ser.groupby(level=0).sum()
```

输出:

```
苹果    19
香蕉    12
桃子     8
dtype: int64
```

#### 多级索引(MultiIndex)

代码:

```Python
ids = np.arange(1001, 1006)
sms = ['期中', '期末']
index = pd.MultiIndex.from_product((ids, sms), names=['学号', '学期'])
courses = ['语文', '数学', '英语']
scores = np.random.randint(60, 101, (10, 3))
df = pd.DataFrame(data=scores, columns=courses, index=index)
df
```

> **说明**:上面的代码使用了`MultiIndex`的类方法`from_product`,该方法通过`ids`和`sms`两组数据的笛卡尔积构造了多级索引。

输出:

```
             语文 数学 英语
学号	学期			
1001  期中	93	77	60
      期末	93	98	84
1002  期中	64	78	71
      期末	70	71	97
1003  期中	72	88	97
      期末	99	100	63
1004  期中	80	71	61
      期末	91	62	72
1005  期中	82	95	67
      期末	84	78	86
```

代码:

```Python
# 计算每个学生的成绩,期中占25%,期末占75%
df.groupby(level=0).agg(lambda x: x.values[0] * 0.25 + x.values[1] * 0.75)
```

输出:

```
        语文    数学    英语
学号			
1001	93.00	92.75	78.00
1002	68.50	72.75	90.50
1003	92.25	97.00	71.50
1004	88.25	64.25	69.25
1005	83.50	82.25	81.25
```

#### 日期时间索引(DatetimeIndex)

1. 通过`date_range()`函数,我们可以创建日期时间索引,代码如下所示。

    代码:

    ```Python
    pd.date_range('2021-1-1', '2021-6-1', periods=10)
    ```

    输出:

    ```
    DatetimeIndex(['2021-01-01 00:00:00', '2021-01-17 18:40:00',
                   '2021-02-03 13:20:00', '2021-02-20 08:00:00',
                   '2021-03-09 02:40:00', '2021-03-25 21:20:00',
                   '2021-04-11 16:00:00', '2021-04-28 10:40:00',
                   '2021-05-15 05:20:00', '2021-06-01 00:00:00'],
                  dtype='datetime64[ns]', freq=None)
    ```

    代码:

    ```Python
    pd.date_range('2021-1-1', '2021-6-1', freq='W')
    ```

    输出:

    ```
    DatetimeIndex(['2021-01-03', '2021-01-10', '2021-01-17', '2021-01-24',
                   '2021-01-31', '2021-02-07', '2021-02-14', '2021-02-21',
                   '2021-02-28', '2021-03-07', '2021-03-14', '2021-03-21',
                   '2021-03-28', '2021-04-04', '2021-04-11', '2021-04-18',
                   '2021-04-25', '2021-05-02', '2021-05-09', '2021-05-16',
                   '2021-05-23', '2021-05-30'],
                  dtype='datetime64[ns]', freq='W-SUN')
    ```

2. 通过`DateOffset`类型,我们可以设置时间差并和`DatetimeIndex`进行运算,具体的操作如下所示。

    代码:

    ```Python
    index = pd.date_range('2021-1-1', '2021-6-1', freq='W')
    index - pd.DateOffset(days=2)
    ```

    输出:

    ```
    DatetimeIndex(['2021-01-01', '2021-01-08', '2021-01-15', '2021-01-22',
                   '2021-01-29', '2021-02-05', '2021-02-12', '2021-02-19',
                   '2021-02-26', '2021-03-05', '2021-03-12', '2021-03-19',
                   '2021-03-26', '2021-04-02', '2021-04-09', '2021-04-16',
                   '2021-04-23', '2021-04-30', '2021-05-07', '2021-05-14',
                   '2021-05-21', '2021-05-28'],
                  dtype='datetime64[ns]', freq=None)
    ```

    代码:

    ```Python
    index + pd.DateOffset(days=2)
    ```

    输出:

    ```
    DatetimeIndex(['2021-01-05', '2021-01-12', '2021-01-19', '2021-01-26',
                   '2021-02-02', '2021-02-09', '2021-02-16', '2021-02-23',
                   '2021-03-02', '2021-03-09', '2021-03-16', '2021-03-23',
                   '2021-03-30', '2021-04-06', '2021-04-13', '2021-04-20',
                   '2021-04-27', '2021-05-04', '2021-05-11', '2021-05-18',
                   '2021-05-25', '2021-06-01'],
                  dtype='datetime64[ns]', freq=None)
    ```

4. 可以使用`DatatimeIndex`类型的相关方法来处理数据,具体包括:
    - `shift()`方法:通过时间前移或后移数据,我们仍然以上面百度股票数据为例,代码如下所示。
    
        代码:
    
        ```Python
        baidu_df.shift(3, fill_value=0)
        ```
    
        输出:
    
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
363
        <img src="https://github.com/jackfrued/mypic/raw/master/20211208220551.png" style="zoom:150%;">
364 365 366 367 368 369 370 371 372
    
        代码:
    
        ```Python
        baidu_df.shift(-1, fill_value=0)
        ```
    
        输出:
    
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
373
        <img src="https://github.com/jackfrued/mypic/raw/master/20211208220713.png" style="zoom:150%;">
374 375 376 377 378 379 380 381 382 383 384
    
    - `asfreq()`方法:指定一个时间频率抽取对应的数据,代码如下所示。
    
        代码:
    
        ```Python
        baidu_df.asfreq('5D')
        ```
    
        输出:
    
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
385
        <img src="https://github.com/jackfrued/mypic/raw/master/20211208221202.png">
386 387 388 389 390 391 392 393 394
    
        代码:
    
        ```Python
        baidu_df.asfreq('5D', method='ffill')
        ```
    
        输出:
    
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
395
        <img src="https://github.com/jackfrued/mypic/raw/master/20211208221249.png" style="zoom:150%;">
396 397 398 399 400 401 402 403 404 405 406
    
    - `resample()`方法:基于时间对数据进行重采样,相当于根据时间周期对数据进行了分组操作,代码如下所示。
    
        代码:
    
        ```Python
        baidu_df.resample('1M').mean()
        ```
    
        输出:
    
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
407
        <img src="https://github.com/jackfrued/mypic/raw/master/20211208221429.png">
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
    
    > **说明**:上面的代码中,`W`表示一周,`5D`表示`5`天,`1M`表示`1`个月。
    
5. 时区转换
  
    - 获取时区信息。
    
        ```Python
        import pytz
        
        pytz.common_timezones
        ```
    
    - `tz_localize()`方法:将日期时间本地化。
    
        代码:
    
        ```Python
        baidu_df = baidu_df.tz_localize('Asia/Chongqing')
        baidu_df
        ```
    
        输出:
    
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
432
        <img src="https://github.com/jackfrued/mypic/raw/master/20211208221947.png">
433 434 435 436 437 438 439 440 441 442 443
    
    - `tz_convert()`方法:转换时区。
    
        代码:
    
        ```Python
        baidu_df.tz_convert('America/New_York')
        ```
    
        输出:
    
骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
444
        <img src="https://github.com/jackfrued/mypic/raw/master/20211208222404.png">
445 446 447 448