数学建模社区-数学中国

标题: datawhale8月组队学习《pandas数据处理与分析》(下)(文本、分类、时序数据) [打印本页]

作者: 杨利霞    时间: 2022-9-5 16:11
标题: datawhale8月组队学习《pandas数据处理与分析》(下)(文本、分类、时序数据)

& ?. R+ N3 J0 {
. P6 \) _. C+ Q  g
( a6 Q( T# ^; h# N2 g/ [文章目录& R8 {9 R& M. g7 A
第八章 文本数据
0 w) ]- T' y. k# \7 r6 g7 m8.1 str对象
* |* ^" t4 \1 @8.1.1 str对象的设计意图
9 Q8 _% [. D, E- f; x% R: t/ I  V$ F8.1.3 string类型
( m) F" [/ Z2 d" Y: w2 R5 r/ X8.2 正则表达式基础
8 H2 `7 a( D6 j8.2.1 . 一般字符的匹配1 K- f6 y( c+ K$ G
8.2.2 元字符基础8 M1 ~/ c2 r+ D+ J  X6 o
8.2.3 简写字符集  @  k& ]9 A2 A, t0 N; H6 I) `& H6 n
8.3 文本处理的五类操作8 j. t/ D+ b. z; h
8.3.1 `str.split `拆分
) ^6 L, E8 c4 `7 T4 [2 m. x' y8.3.2 `str.join` 或 `str.cat `合并
$ K0 p' Z6 q3 d7 a$ D. L$ Z8.3.3 匹配$ v0 }" Z3 c, V6 W6 ?
8.3.5 提取
7 c3 r* Q5 T8 t. w6 s6 u; y8.4、常用字符串函数
) c6 H# Y* w: e) W8.4.1 字母型函数% C* }# F* J7 B$ U* Z
8.4.2 数值型函数
. Q% I) x' I$ r: [7 h+ R( V8.4.3 统计型函数
0 x8 A) i" l3 d- o& ^( c8.4.4 格式型函数
) `2 R9 H( u4 x9 P4 l8.5 练习
) G# X  m, U8 H6 Y9 V, l( QEx1:房屋信息数据集
) {- i5 Y5 K* g1 y2 E$ GEx2:《权力的游戏》剧本数据集1 q; H. s" I5 g* I* w
第九章 分类数据
" e  J6 S0 \1 e! b; N9.1 cat对象
" t% P; ?5 o6 N' K1 t, |# a9.1.1 cat对象的属性
! o- r' T, m. W/ z8 t3 z: B9.1.2 类别的增加、删除和修改# }2 u" ^6 p6 e6 X: Z
9.2 有序分类
6 b  @& ~, U, @# ~- R9.2.1 序的建立2 t/ F1 z+ k9 u( g- J5 X, r9 n
9.2.2 排序和比较/ S2 y$ }/ O) l- L" [
9.3 区间类别
# X4 |  g! L1 A0 p1 b- Q1 W9.3.1 利用cut和qcut进行区间构造0 s. @. M6 q7 \' W
9.3.2 一般区间的构造2 Q" H( H9 T, W' d( r) c4 D
9.3.3 区间的属性与方法
; g% b/ h/ b0 Y" l7 h: `9.4 练习. a  |* y8 O/ k) S4 j! `
Ex1: 统计未出现的类别3 O7 e9 |: L  A6 d9 O" L
Ex2: 钻石数据集
8 U/ p$ v) y- Z4 C. w第十章 时序数据
& Y( ?) v' B1 \5 v' X; y3 n+ w  v3 H10.1 时序中的基本对象
* W; n8 U. _$ j" k. r& j10.2 时间戳1 e) g5 c8 |4 R) g6 h' c# X
10.2.1 Timestamp的构造与属性, ]8 I" O6 `( w+ Y! e
10.2.2 Datetime序列的生成" I! F% _- ~/ V. m$ e% |5 ?! K
10.2.3 dt对象5 w) }, ^+ G  D5 y) O  U, J
10.2.4 时间戳的切片与索引9 S( _+ Y8 e) f5 ?* B
10.3 时间差
. T4 `- Y0 h5 Z2 \9 Z# H10.3.1 Timedelta的生成
5 r  Y; I- g* A10.2.2 Timedelta的运算) E. t/ s0 J5 Y( ?$ ?- j
10.4 日期偏置
+ j4 m; h. `; T10.4.1 Offset对象4 R- A* H# C6 a0 C7 I/ W
10.4.2 偏置字符串7 ^% B1 i3 F) [
10.5、时序中的滑窗与分组
2 p% R: t$ U. o% m% u6 _10.5.1 滑动窗口
' Y- k+ B7 d/ R3 y7 c  h. I5 z10.5.2 重采样
) O& x  j6 H/ h" ]5 x) n10.6 练习8 c8 t6 t' A0 W# ]
Ex1:太阳辐射数据集
: b) \1 O# Q& b; `$ {2 H, U' G$ REx2:水果销量数据集3 O" i7 W/ s8 c/ B+ h/ I1 k
  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网8 h; t* R( c1 B4 {+ L6 {
传送门:* K' w! n  J! f) X% F

& U. p$ k+ O! l% k4 H# ?datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组). Z. c- y* h( h# k5 V0 u$ }6 t. ~
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)( o. `0 s/ p4 z* e6 I. O
第八章 文本数据  I, A7 U& x: _+ \
8.1 str对象
, t1 A8 l  N+ T' k: L8.1.1 str对象的设计意图$ M- C( {! z7 a* R) L* Y8 z9 c
  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
2 U' k0 C  f; e5 A# M
1 A; p; I! ?' p% O4 Zvar = 'abcd'
" F, x' n+ i. l5 D6 h; G6 G4 V, }str.upper(var) # Python内置str模块( \) ^' A/ s; T0 P' m- e  X) d
Out[4]: 'ABCD'  ~+ d, f5 l5 I, G8 G" o1 H4 t

7 a' C9 G; v3 {& ~+ Y6 ls = pd.Series(['abcd', 'efg', 'hi']). n% {" Z) S0 S. Z+ ]9 @! ~
9 t/ _- o; B9 M
s.str
2 E/ v7 w6 ]% y1 K" m  z! wOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
+ P- |. o/ O& z1 Z6 ~" {' n' ]) v( ~
s.str.upper() # pandas中str对象上的upper方法
+ q1 e6 _4 L* Z) b( LOut[7]: 9 r: W. y; i  U8 |  r
0    ABCD8 D. a- V! Q- [6 g
1     EFG  o; v/ E& l* @0 c9 L3 ~8 W/ ]
2      HI
$ ?- ?3 W1 R3 h, q2 u, Wdtype: object7 i2 `1 k' T& l+ O8 M6 S+ r
1
$ H& t- J! E9 o$ j) G& d26 a) C8 @3 w$ \- F
3: L, L3 b6 i0 }9 X7 F+ s) V
4
% h+ @( F3 w3 C. \5 i  a4 n5
0 i/ @# a  A5 X- j( p8 O3 O6
7 F2 U+ b7 m. b8 |/ [: \: K7
- i7 Z# f8 I4 n0 E4 |( N8
/ e# x5 i8 s+ p" i2 b' V4 i9
8 V" i5 [& T% l' e! Z. P# p10
: \$ ?) {0 b3 ?- w" l/ T" |: U11
2 {- ^. J2 [' M5 d& {. }12
! [  |& p: c1 u13. ^) F# J5 t9 V2 P6 C$ L$ C
14
% G, ^5 o# o% N& v  Z) P6 {# \15
6 b5 `  ~: _' S+ c8.1.2 []索引器
7 u3 c+ ^/ C' p) c+ \  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
' {) F9 J  W: \, I7 A: y/ h  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
1 f8 S' p" K3 B6 N  ?1 r% ^# q+ W, ?6 A+ `( ]  ?1 {, a  k& T: I
s.str[0]
; p7 X) J; v% e& Z" GOut[10]:
  G. f4 `2 g  n0    a
  x0 S( t3 k: h. q1    e
) {6 t" U, {& b2    h
8 i; @+ i3 U0 B, r  h0 B3 hdtype: object& R( M' @/ U% c* J+ ~. v. `
9 i8 C) l& A! y  p- p# `
s.str[-1: 0: -2]( w- b( D9 s3 z! P6 S
Out[11]:
( b  j6 l9 X( H5 n4 f. e4 q0    db) l. w  p$ u# A/ O* o" `: y
1     g" v" A! r( l9 M7 l3 ^; o+ [
2     i! W- J! I4 t! w: ^% Y: k
dtype: object
7 r) k. \: k4 P) o  X# t: ~
  v8 h" V( Q* l8 s) N% as.str[2]6 R& N4 A6 M. @
Out[12]: # r- X7 D, P3 K0 c8 F) B
0      c' B) f6 B. Y6 N1 A
1      g( ~$ J. I1 w0 C  P, ]2 p' E! g
2    NaN* u, k, L% H# N) O9 W
dtype: object
; ^, \, B2 F% d
% D& e9 ^! I( D% \+ e; W1
- o3 f, k. \# f27 E" {9 t9 O! _8 s0 n
38 W! L8 |0 ~& T( G' B4 O# _
4
3 y: ?; q" N. k9 D4 k5( g$ ?: _. V0 c3 a0 f( W1 t7 J( @
6" J% I: w7 G* K+ t
7
. V( h1 A, B: V, F% u0 v+ [85 C% ]& Q- [  r2 M! x& N7 N
99 R% x, x. Q: X! G' [$ ^- J$ c" f
10
% d& L: |0 |' C+ d11
! L% F- W. u/ i12* l0 T1 R/ R6 g; s0 R
13
: Y4 [; \$ `& v2 }7 @( Y! Z% B$ d14* s3 g4 t' F, y' A; Y
15, t9 N4 O& ?) q& s$ K% a( F
16
, g+ T- y# t" Q, ]  F" X177 f  p& Q' \) @* g. r; R
183 [/ H9 B( k7 L1 m/ x
19% X2 O1 G4 @8 E  p' G
20& u$ S' O+ t. r7 K7 Y6 q: L
import numpy as np# K9 X" O$ u# X  g/ ~3 C
import pandas as pd  b; ]  y) M7 y4 c. p0 ^

" j, a/ m( W6 i. q3 o) ]  _s = pd.Series(['abcd', 'efg', 'hi']). p9 n$ F* p$ g$ z
s.str[0]
; c) L) T1 v% a* C4 F* F; U1+ P. M! h/ Y; M; {
2
8 k! b/ s- Z- }35 b9 E/ F. O% r- u+ J4 z
4
3 Z# i) [8 H5 W+ Q" ~4 d59 X8 K: P0 ]( d6 |$ U4 j4 f; e: P
0    a
; s$ r3 q$ A$ |$ s: X3 Y+ G8 e% G* x1    e
3 U! g/ D" c" K4 A7 I3 o" r2    h
, Z* P( E9 R. X2 U; xdtype: object
  J; v6 n% E0 c/ Z1; U; b% R5 R# h/ {4 o1 j
22 v0 f/ e  s5 P
3
& L& n* _6 ~$ k. v* J; t* I4
% G+ q" g8 u6 ?* o8.1.3 string类型
+ g' C/ f( S. j. @6 W  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。! W1 z2 o, p% g% ?: c2 [* v8 c. T
  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
& b2 E* M  {! B# _# e. G1 f  J$ U) E7 x' ^
二者对于某些对象的 str 序列化方法不同。* Q4 }* o6 G: S% ~3 W3 a: T
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
% s' d- e' s4 C4 \' j# [2 z' G- R$ qs = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])/ o! u$ n5 j7 @0 k8 U
s
1 f5 m7 ~9 o# @5 v1
- Y, m8 H8 t) D+ O' Y7 b$ X2& i2 G3 e% ~' H2 b
0    {1: 'temp_1', 2: 'temp_2'}' F9 l5 a0 ?8 X6 K* D* I; b
1                        [a, b]
! D/ O  [# T! F2                           0.5; R5 _5 |5 _2 ~- t) l9 o/ ^
3                     my_string* J9 }- Y4 U, {
dtype: object
; Q8 c8 t* a5 z7 r1 g8 X1& ~! A& s7 l+ G& |: a5 \
2
. Q+ C  q! j+ l6 h4 Z3
1 B, ?3 X! u3 v7 t4
9 _: Q# }3 @* h5 N5
' W; ?9 A* Z* H* A9 T, Ds.str[1] # 对每个元素取[1]的操作
$ r  d2 s, m* q+ x$ f18 }, _; g3 s2 L7 h+ R- Y
0    temp_10 w2 X+ p% p4 ~& Y% Q8 {- i
1         b
5 v# F3 X  ?" W" K0 G9 ^  t0 E7 L2       NaN
8 t+ K1 v1 g1 I% J3         y8 x5 M, o! B7 \2 V- l
dtype: object
: V) Y! }1 L& t* O, w1
8 C! r0 \3 ~# v; J2 G. J2
/ u2 x: h$ z8 D( E' |6 m. c6 j$ k' j3
0 f  R3 ^9 e2 i4+ B& l; [  q2 D" T4 M
5
! r, G/ b8 Q2 S0 m: O1 qs.astype('string').str[1]
; y& d! R6 E9 s4 b- ~/ s# T1" \% B, e2 V9 M
0    10 x( Q# j' s! K! }9 e; A  l
1    '
9 ]; p7 n3 `+ Q0 m4 C+ u3 V2    .
; q3 m( x$ U2 T; [5 Q# S" S3    y
4 m+ ?* Z+ A& {) x: B9 z8 l+ u$ ?/ V0 ldtype: string8 a5 c5 ]! z, C3 c6 M
1+ }, p. N2 N; u- W9 }# ^
2! v% H' G5 O8 P. B) g% W
3
1 s1 y& e* l9 E8 t0 [4 i5 N4' E4 ^! }' h$ J: n( M
5
5 O8 F: r9 u+ Y8 {2 H除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
7 }& V3 e0 d* q* y1 {! M, ~/ j0 I* B4 i
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。+ d  }6 M9 U0 Y# t/ m! ?9 J8 v
string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。% j0 g4 i6 p; i' Q) `
string 类型是 Nullable 类型,但 object 不是! e# o; `1 u8 m% F
  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。. A. j$ `8 ]# M; A" D2 q
  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。3 s' c4 N5 [- N% V5 Z+ v: x
s = pd.Series(['a'])
, Q* w& d/ j7 \2 R
9 v. s  l* _9 F9 ]4 U% P. is.str.len(): ~) f* h; i, x* n
Out[17]: 6 w6 m! n& `5 x9 y# ]% B
0    1
+ I' p  C" ]& Y5 v; R9 a! ]dtype: int64; s: `: N+ R' @+ I
: `# \  e8 Z$ f9 L3 c* B
s.astype('string').str.len()
% b. g) F5 U7 r! r' a/ sOut[18]:
; T4 |5 n9 w* D! f0 M' r0    1/ |/ L0 a3 Q. Z% x
dtype: Int640 ]; F1 S* B6 y3 W2 K1 b  f) ~

7 q, K* k- B+ e. c- Bs == 'a'
/ H0 V4 `* z: p2 Q' tOut[19]:
2 [+ i+ D& P2 D9 j2 Y# {) {0    True# B2 Z* G8 P5 v% v* K
dtype: bool0 U% }8 [$ ~/ I9 o! f) T% N
" V2 ]& V% q; K7 P
s.astype('string') == 'a'* B% {2 \3 r6 q  I# D
Out[20]:
7 `0 N  b2 C+ B* J4 H" ?0    True+ d, l( @* O! _# X$ Y$ ]" r$ D  X4 j
dtype: boolean
5 T# r1 O- A& J8 T; O
2 ~( {* U1 Y! u  F4 w3 hs = pd.Series(['a', np.nan]) # 带有缺失值/ J; h) k; m3 M7 T+ a/ |8 ~
0 w) r1 V+ Q- ~
s.str.len()8 u2 E! S+ E% E* s; L3 `
Out[22]: & a. T6 W. j7 [, C7 m, s
0    1.0
; }6 A' W. B$ V! O* z% H$ Q1    NaN8 Z  b, c% M( x# j
dtype: float649 {. ~9 w: L, }0 _9 \% E% p4 |/ @% S- ]
7 O* S4 e' q& R
s.astype('string').str.len()
0 n" B$ W1 e' DOut[23]:
  _  R* H9 _+ W& e& P, ]0       1
5 @! P& P7 N+ u* Z5 H9 C' E1    <NA>+ N3 ?5 ?2 A& O" T, I1 G6 `
dtype: Int64+ C0 r1 p  ~6 X6 W2 b8 ?
/ Z) y2 \5 h+ Z& K' ~$ @* T
s == 'a'
- @9 g) @/ v* `8 s: t3 UOut[24]: ( X0 i' E8 J* X5 r
0     True
+ D* v* H6 G) Y* l! R2 }0 |1    False  b$ l" Y( ]0 l! n- B
dtype: bool& w% X" G# o1 h0 r4 ]& L

9 H4 H' e- F* a6 D  _, W. W* u9 f7 J( Js.astype('string') == 'a'+ K* J" ~) f* m' c0 d1 U/ f
Out[25]:
$ ^9 y, h& M5 K9 I& L; c0 Z1 N/ r* W# j" I0    True4 i4 y' x" i# x8 Q+ e; a
1    <NA>
& K4 v& K7 d* ^0 Odtype: boolean4 O- r  b8 ]4 [7 J) R! \6 i* l9 l! T
* j. r' C7 d/ Y3 M1 ?
1
; s0 v& W5 E' d8 _26 H/ F! ?' @) |7 o% F
3
' }; x0 G& M  C/ B4" K5 j/ y4 b5 J& ~$ M5 E7 _7 b
55 Y5 j- h' j6 I+ |( l. @4 z
6. q- F3 Y  d/ [* N6 C8 A
7$ A! {. C( J' k
8
/ F9 y2 E& D. l/ ^5 u9 O9
" a7 u3 C( O9 F) j# z3 i10' x" B. [) @! L7 ], s7 ~  U
11
, X- Y$ S" D% l5 }$ W, g12
( ~( I/ |4 p2 C& S( [" K! T13# Z* \0 b9 G/ y6 B) W
14' R& X; Y  w" `$ m' R
15
1 h$ i7 @/ N; }+ @1 R6 M% k165 c$ V) h* M, E4 R& I6 Y
17
" ?0 [' _6 i1 P18$ {  r" Y3 q7 u: B) y* J1 j6 y. V
192 J' [  B6 F6 p( y8 ?- P" K8 ~
20
+ l5 Y! @$ @" H( C, c5 P21% j7 ~% m- \3 V; F6 {; {' b
22# O( |, x+ ]# @- E% n
23
4 @) x; v  H5 T8 s& J4 g24
7 N4 |5 {1 e) S# n, E0 O4 s25' z2 e4 O) m' i* \
268 h1 `# C  {- h
27
+ |8 l) y' Y  h7 j+ |7 K28, ]8 f2 g2 o& q4 I- z6 Q
29
! H/ P" y( `* S9 {. J. s30
& d2 T1 `3 S$ a31
/ W# Q6 V. v, V! z1 x32/ r" k1 {$ j5 o% G8 y: L+ L
33
8 a) V2 D! h" D34# Q. x' K, A0 h; u
35
) ^) l/ Q: t$ h& f+ @. M* g36
. z/ ]9 j3 B, v* i- s% u2 p7 N3 t* R37
2 q& P6 E- b" o* Q& l38
0 n. ~) T$ w* C5 r398 Y( e5 h" u8 ?0 K  M
400 m3 n6 g& e$ _5 t& H% ~+ C
41- e7 Y) J& K4 [
424 c- Q5 H5 x/ c/ p
43
4 t$ H# m/ z0 P' n& s) z44
! j+ B& t) m- b7 |1 d& ]45) c; h% y0 z1 W6 s* i1 e( \
464 x# ]# ]" \& a2 Q
47: I9 p- I9 _. o: z- V& R) o
  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
$ t. ~: m5 e5 J6 [8 J
1 |) _% F% Q( P' W# G, Us = pd.Series([12, 345, 6789])& w8 I( S3 S' P# A. ^- i% U3 H

% [/ x& w2 j+ ^9 l2 n5 us.astype('string').str[1]7 o. a9 C7 i7 s5 L
Out[27]:
0 M! _. A3 n9 a8 e3 V5 p) s0    2
: r3 a) f  L$ i1    4- ^/ n$ P: `( I( d
2    7; p- W" v, B" w. C$ W) e/ @
dtype: string" `% c- T8 ?5 x. x3 _
1
: b; a1 D  ]+ {2
8 E, f& V% n0 @; y/ U: c3
$ i/ a3 b, h; ]' w+ r# K+ }% ]4
! W3 U3 u( }. _51 u- H* p( N8 U2 \
6: c6 U6 \4 o& D/ r" x
7
+ S/ ?3 h7 v1 T2 j8" i$ t& T2 K+ H* Z
8.2 正则表达式基础, p5 q( E) P$ V' ~/ v- J
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书% Q, v- a" G9 i
% ^0 `  w9 i- z! n
8.2.1 . 一般字符的匹配& m' c6 @8 o" V' B
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
. N& W" U& p$ k+ c
. p  C+ i7 v2 U0 L/ n. Timport re' L1 s6 r- `- r, c2 o& D
2 C1 v; b. Q$ a$ i/ b4 f* K
re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配  I7 L5 T. e7 p5 H; n: g& @1 F6 j
Out[29]: ['Apple', 'Apple']8 A. `9 ^) L% Y% m! }6 q$ N
1- j' _$ V5 Q" n; z1 t
2
$ U9 k+ u8 l( D) H( W4 G3" O4 |1 \# D+ B! B
4
) q! X8 G; }+ ~1 D  m9 ~8.2.2 元字符基础
6 ]6 W+ w8 E8 }: W6 u元字符        描述
5 D% X' Y( k: ]& V.        匹配除换行符以外的任意字符
: Y3 m9 F( ]+ d1 f% x6 n5 R[ ]        字符类,匹配方括号中包含的任意字符2 Q5 Z$ i  o* E- A! O! ~
[^ ]        否定字符类,匹配方括号中不包含的任意字符
* M: q7 \/ z7 ^" U9 z. o/ O*        匹配前面的子表达式零次或多次% H7 y: Z# K  m" I
+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字9 t% d1 B! s3 ~8 E1 ?3 o+ n9 `
?        匹配前面的子表达式零次或一次,非贪婪方式
8 Y% q2 g" r  v0 }( v4 q' N4 x{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
$ ?# F. z' D" d1 w0 ^+ U(xyz)        字符组,按照确切的顺序匹配字符xyz2 i- K$ q4 o$ n# `" N
|        分支结构,匹配符号之前的字符或后面的字符( C* Y6 d6 c; O! e# O
\        转义符,它可以还原元字符原来的含义
1 X( u! C9 }0 s4 K^        匹配行的开始
& n, b: ?+ b) W" f/ ~* E$        匹配行的结束
$ K- B8 N* {# y) y8 G# N& Gimport re/ t8 y* s( x0 z; m1 W, m
re.findall(r'.', 'abc')$ l) A5 j" v2 v# U0 u: [. w; f
Out[30]: ['a', 'b', 'c']
+ U1 S2 G- X/ N1 y! Z) ?6 q7 l( \1 ?6 m  E) A" k3 m1 E2 j
re.findall(r'[ac]', 'abc') # []中有的子串都匹配
% B5 r. H9 Y* x) TOut[31]: ['a', 'c']8 f" q2 k5 d3 I1 T

7 ]; h; _+ z8 M5 f* j1 x9 Rre.findall(r'[^ac]', 'abc')
& W- S' @1 R6 v" tOut[32]: ['b']+ b" ?/ s" m9 I

& c/ X3 K7 b* o) R1 {re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次  L" @- o3 B+ i% v" E! x. x' }* X
Out[33]: ['aa', 'aa', 'bb', 'bb']
3 X: o/ W7 b) @! d4 @/ m# V  }7 j% v* p) V$ ^/ t8 |
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
7 S9 w, s' }$ Z8 M- ]' jOut[34]: ['ca', 'bbc', 'bbc']
8 b/ |' T! g) D; ?6 D. B) \; p2 ^/ }( c- i% O
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。- k5 `2 \- L1 D  m* {  o
"""
* W; s: o$ O; p1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。7 |1 m+ m' u. @* ]1 t9 [( x
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边- Y  ^% V6 `3 m$ E
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
$ a/ p6 L, I& t" z, @但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a, N# z' F' B3 y  x4 k. a
"""
* a9 |4 f' ^  l' L. @2 u
- H9 ^6 M9 k1 @! are.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。+ a. I+ L( O3 [& B
Out[35]: ['a', 'a', 'a', 'a']+ s; l7 P0 r6 x8 S' j9 e. Y1 t
5 ]" S. m$ J) Z) P, I3 n+ W1 n
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
, o1 t$ P; S# v, s, \# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。+ {& R, @- d& C) L; N6 q/ b
re.findall(r'\\', 'aa\a*a') + f+ A) Z7 @5 q; M8 F; i) |+ I
[]4 P5 d) Y3 |1 h+ q% @
$ D' R! z4 ^7 b' E  C9 h
re.findall(r'a?.', 'abaacadaae')
! A5 ?" R: ^) C+ g$ b$ g. _: BOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
1 B0 U7 j5 X: J$ }$ v) U6 a) ?, F5 ]+ O& t6 [2 a
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表( Q( H' B9 d! b- L0 r
[('width', '20'), ('height', '10')]
! o4 T. ]: X" j1 [
3 A# N# ^2 g& [2 j3 S- d1
2 D" F5 q$ v$ t2. h# ~& H  ]# u& r' d# E8 c8 {4 l9 z
3, L) e7 A  a: Z, i& j
4
: o' U$ \3 D) n  d5
) i+ n$ t0 I9 [0 l6
: I3 Z6 A  h% N2 c. J" a, T78 W7 G1 t: ?$ ^* R5 t$ Y/ ]( L
8
, ]' e9 E9 j% V$ S0 q# R0 L' F  I/ u9- I9 [; P" G! I. l% ]$ I
10
4 W( r* x' h8 V* Q, @11- E1 H( P; C: \& R  V7 a  d
12
' Z% u9 y7 x+ Y. y; Y; q7 A13
( L2 o% g$ d0 g# l0 W( Z! U. Q14
6 Y. d, V; G2 K150 _7 d) f% g9 h+ k8 o7 U
16& @1 m$ _; w- x4 ^0 w1 a2 o/ }
17* Q" ?( h, H/ E0 X2 i
18& a- L4 l, s" v  v8 a0 C9 C
190 g2 @3 M! B2 m4 i( u
20/ r, [8 x3 _" T1 Q# I" V
218 S8 D) y' [" `/ p1 }- P
22- N1 O& }* x3 u/ d2 E& @
23) _* z* x& z1 r& T& O" o
247 G# s5 Y! M$ t# N5 ?
25
: p7 d2 [; H2 r/ W& d, }26
6 V3 u% v+ W, S* j4 x27, _2 x* y7 P  f; i# G
28
- M& u" g* u& f5 g  a5 D) E4 B29
4 p# \+ f+ M/ X; A! x" X4 w2 Q30
+ S+ l. K& D6 [3 ]! C' k31
0 Z& M9 Y+ d- k% f8 {32
; ]  j1 J* q0 C7 J33, \' R: i4 Q- ?% |$ f
34
5 D$ H$ w' H/ Z; ]( z# N+ ~2 g35
1 I( F6 ?3 V/ x2 `- D36
7 _4 M" ~! z  |. I37
$ M  V7 p* D$ }  s% I' ?) @) t  c8.2.3 简写字符集% f/ N- h, l- s7 X! W$ h0 G( S
则表达式中还有一类简写字符集,其等价于一组字符的集合:6 u& n  Z* J+ t' @" Q& [* J( N
7 i( P! g5 h# e; g
简写        描述
! C2 c5 E( ?2 c4 A( ~; g3 e- N  a( \\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]9 ?5 [! {! n+ @: o6 T
\W        匹配非字母和数字的字符: [^\w]
4 ]) w0 n6 s, r& c3 N1 x, y' U  y\d        匹配数字: [0-9]
0 V$ @" V1 c- D\D        匹配非数字: [^\d]* i, n2 J9 q- p- M
\s        匹配空格符: [\t\n\f\r\p{Z}]
* S5 }5 P. K5 c\S        匹配非空格符: [^\s]% b" G# D( }9 x. C8 T- V' i9 c
\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。! A# K( u1 L9 J: g
re.findall(r'.s', 'Apple! This Is an Apple!')
' a/ y5 Y3 J# d* HOut[37]: ['is', 'Is']" ^8 F9 E+ K9 }1 J; x7 ~
- r9 `& o6 z- O" O6 h
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
) C( U7 I6 f/ g- ]& E% o1 YOut[38]: ['09', '7w', 'c_', '9q']" A* v5 Z( s4 e) ?3 Y+ N
1 w4 \' l5 _3 q
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)7 {9 F' \7 W; P3 X$ x% Y! u! _, I1 t
Out[39]: ['8?', 'p@']7 g* ], k0 L* o0 l

) _+ r. P4 H1 R9 vre.findall(r'.\s.', 'Constant dropping wears the stone.')5 H; c/ Q$ }9 t2 d9 P! ]$ V
Out[40]: ['t d', 'g w', 's t', 'e s']1 Q0 h% P3 G$ p$ G0 K" m* ?& A' W

) w7 v  K8 L/ H; i! p5 ure.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',5 ~/ [5 K3 V% c0 s+ z# w
           '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')4 l5 [0 t" H) {

# j6 F! X- ?2 g; E5 `$ WOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]3 G/ @7 q) I( Y1 ^

* Z" A8 W, Q1 x& Y. P7 Y1
4 O$ Q, m% U- n& A. Q  I/ D8 b- m2
" Y' x, p7 B) w30 [  [7 f9 c8 Y, _4 ]  |: T
47 ?4 E3 R  T# j5 S
5' ?9 q. k/ G9 ]: W% y! ^
6
& j! [7 R! x! @/ m3 S7
" [/ ]4 l& g: a7 a' A) b5 C5 }8
' _: r7 l0 n3 G: H* X& a95 {! k9 V8 n* G1 _' e
103 t' S! A9 \; N' M+ E% p+ t" g$ b
11
% J6 Z: ]3 \  {+ B5 c; l5 T129 m9 O0 x& W' }( B" J
13
) f5 q+ m3 z9 T' h4 u145 B. C) I( v& r! |
15' d1 f) @/ a% t
16' j: T( m3 Y0 d7 O% ?3 ^/ E. _
8.3 文本处理的五类操作
" `0 A( `; k) B, t5 h" f8 F8.3.1 str.split 拆分
6 [( ]( g+ ]9 I; E& Q% U* s. L5 F  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。5 ~( N" L' l4 d) i% o$ D5 a
( P; I1 ~8 `# J
s = pd.Series(['上海市黄浦区方浜中路249号',  ?$ y0 p; o( E3 f! X3 A# j
            '上海市宝山区密山路5号'])0 y; b& Y& X) f

! ?( H9 Z2 z) u
3 t$ k2 r$ C- t8 \( Hs.str.split('[市区路]') # 每条结果为一行,相当于Series
. A+ U$ t3 o5 b* SOut[43]: ! C/ o) E( t- g6 z+ D
0    [上海, 黄浦, 方浜中, 249号]
! O. F+ K/ E" i/ C# s1       [上海, 宝山, 密山, 5号]4 ~6 S/ `/ S/ i6 t" f* e. ?" r
dtype: object
- Y4 {# c5 A* ~
5 d) M* w. n$ ]7 D6 F' ns.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
: |3 x1 S3 d. G# @Out[44]:
$ v: e5 ~0 ?& h    0   1         22 T( N9 k) d, y9 c7 V6 Q
0  上海  黄浦  方浜中路249号2 A5 o" ~+ l. {: S5 A* [
1  上海  宝山     密山路5号9 z) i  Q- e" O
1
" [& U. T* q9 H4 _* q% A29 i* R- a/ l( \0 O+ ]8 B6 r
3( C" }- h5 g; z  t9 s- e8 w3 a
4
" h# R5 O4 O1 T# |3 k5 R* ?# x' l57 |- w8 K% f. q/ \' X
6/ ~- q& T2 T) h1 p
7: r8 E- k9 F/ n% s. }! o
8- f3 j" r6 r- _4 ~: [/ r8 s/ c
97 m3 `+ G7 Q% _/ p
106 s- p; _2 d0 S9 ~+ @
11
8 j1 j2 W  Z  l: X* N3 ~12
' _8 F; X" E5 ]13- J; {2 y7 ]+ q, _7 Z5 @7 ?
14
) L+ J1 |$ a+ v) ^& t9 V0 S% {154 L+ h/ I( J4 K/ \7 O( M  f. M
  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
1 d! a  z$ h: q3 c7 E
2 w9 Q, r: {/ Is.str.rsplit('[市区路]', n=2, expand=True)
: A6 k* n6 V4 Y5 YOut[45]: 4 t1 D0 b- Q! g' p; U
                0& i: M4 b. R9 T9 E
0  上海市黄浦区方浜中路249号% e7 F, F8 g' k* B1 p
1     上海市宝山区密山路5号: a" J2 I/ _# d/ M0 p7 u1 i
1, p: w" O4 D# Q
2
! s9 q. V- T" ~$ e3; A/ }7 O# T( @. d1 ^. v
4
( y7 @* u0 O( g3 s54 W1 M  P& U! l
8.3.2 str.join 或 str.cat 合并
( J& M6 h3 t1 Nstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。/ [/ D& B4 t+ ]0 z# J! Y
str.cat 用于合并两个序列,主要参数为:$ z; _7 |# P& P# s4 G
sep:连接符、
# m% j% l9 @8 y2 K5 ajoin:连接形式默认为以索引为键的左连接
. N  X4 K/ \5 V; B3 ~na_rep:缺失值替代符号- G9 B* `- y' b  y) q
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
( b. F$ S  \9 _& xs.str.join('-')
/ o* _! P% i2 B: g2 T$ qOut[47]: 0 v5 s/ T2 T" h9 d3 n  Y) |3 y
0    a-b
! j/ F& k; J3 Q! I1    NaN
" E. S- n: R7 X: o8 h+ S2    NaN- R3 J8 U: _% F+ G. @- k0 ]4 N
dtype: object
+ o& U9 F1 K, H2 W* j  g8 k* [1
2 \* G3 W+ v+ g/ C/ X  k# p24 O+ ?. I; @& s$ G! z1 x9 F3 r
30 y# z$ y% ]$ F
4
+ Y& j( }) @7 l( ~) O9 j& W0 W58 ~7 S- {  C6 V  h
66 a. ~; t3 |4 Y# u( y
7
" j3 v/ \! \/ ?7 i- Rs1 = pd.Series(['a','b'])0 \4 c, D+ C. B; b/ X1 O
s2 = pd.Series(['cat','dog'])
" u" e% W2 a1 q+ @& `s1.str.cat(s2,sep='-')5 J  k6 U  z, ^. Q3 Y
Out[50]:
) b2 V6 z4 s% O, _7 J0    a-cat, t, b' ?0 _" s6 L8 f; @& E- `& ?
1    b-dog  t5 w. i4 b  C, j3 y5 ~
dtype: object0 r9 u3 F1 N0 [+ j6 g( Q
$ S" c1 V& K7 e' V% M" @, l
s2.index = [1, 2]( |" f7 R. `  y$ Y, u# d2 ?
s1.str.cat(s2, sep='-', na_rep='?', join='outer')
, T+ Q; O# P1 x# e4 x- I: ~% X+ bOut[52]:
/ ^- s# m! p8 H! F0      a-?$ G! v" [- G7 a
1    b-cat
2 |4 j1 ?: k" |0 ~2    ?-dog
1 m: u& r- x2 q  ?2 vdtype: object' _6 a6 Z: c$ N6 \* O, H
1) }1 k& M, M, V# a' V# }
29 z$ b) @; j6 J/ o6 T! j* g+ ^
3
7 v0 w6 P4 ~8 i( q/ y( T% j8 `4
/ T* u! D2 n7 F* t& h5
" Z; _' S$ f1 U6
3 T- p0 M+ {8 l7: f/ \+ @: `0 k! p
8+ z* e4 e, h  L5 k5 I8 Z5 R
91 d. R$ i+ A+ L9 T0 ]
10
* n7 i6 [0 ?. b3 i% g11- N1 U2 {* |  B8 Z! ]3 i$ o
12
, j+ O$ c* x( w5 O( j) |13, [0 ~1 F/ m5 ^2 o
14$ T& U( l( a6 P' C
15* {' M6 G/ _+ h* j8 Z# y7 J
8.3.3 匹配' L; }7 }* e6 A" q* }. P
str.contains返回了每个字符串是否包含正则模式的布尔序列:/ W& c& X! L8 R# u/ z4 `9 O( g$ L- T
s = pd.Series(['my cat', 'he is fat', 'railway station'])" ~/ A- p. C1 _
s.str.contains('\s\wat')1 b0 I% O7 X; o' n  x1 |

/ b4 F6 K+ C6 D3 l0 Y" Q/ z0     True
/ W# G4 u7 V* b/ a7 u1     True
1 U7 B2 j9 v8 W0 m2    False5 o7 A- A. y1 \% z- m" K. F5 J
dtype: bool
. q! o- G0 C' }% k# o( Z0 q$ C14 P: L8 \! t" y( [% D& ]
2
' v4 l0 `6 l. n2 j4 Z( Z5 d1 K3$ E' y0 }0 W: C! y# b6 {
4
: Z, C; n! ^0 {  c0 _; Y# P5
( }0 h  Z) `& c$ u) W6
' T  _6 O1 @5 [; w9 l3 J% R+ H7. R+ H2 N! v3 i* y. R9 {* D( N$ p
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:" o' W! X( i: ]3 O% v' F
s.str.startswith('my')
. Q% m0 |5 ?$ K% O) G" I
( U: i7 d$ A% ]6 d  K/ [0     True2 ~+ h& k, s; G& |9 E) z
1    False0 ?; j# w; J4 t) a% n4 _
2    False- d' e2 b6 A$ P9 l! U
dtype: bool$ K; F. S$ A+ W5 n1 h& E
1
3 [3 g) R- F- k/ `/ G$ v6 J3 t5 w$ E2
7 J) k) ^; G, X) N3 i3
7 i- O* x* y. F; f4
, q6 S& F' N: u1 c2 e1 N5( z" ]- l8 h7 S
6. v- W* L+ Y2 x; U/ P5 j) X6 d
s.str.endswith('t'): q) N9 @$ [) P3 C3 R

! z$ ?8 Z" A1 }6 u; q% W) R0     True' o, ]- ^0 h& i7 l) G: c
1     True/ q0 d3 E. o( Y  D/ _1 ?
2    False
, s- ]* w& B" `' W3 F3 H8 wdtype: bool
' g4 O) O: ]) R4 D4 u1
2 B# ]1 i  Y+ v' J" j28 G' o* {' u) `8 u
3
2 w0 a& i/ O  p  T9 s1 j. Z4
9 o1 ~/ G( |1 Y2 n- Q5
7 \8 G) J* c+ O4 i6% j0 ]# q0 v# ]6 z
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
; O: e! D+ m- _( n  E3 q' Ms.str.match('m|h')4 u0 D2 X  B/ [" y3 a8 ?( \
s.str.contains('^[m|h]') # 二者等价
! k9 q9 E$ @5 j* d( b# `# E
& U# L, T$ N6 [; y9 m  E: m+ ^* S0     True
% C& T# Y2 F: c8 ~1     True# ~- C9 j( S  ]8 A
2    False
  v. `  b* C8 h6 f# c1 {$ Bdtype: bool4 M3 y+ b, p/ `# I) F3 X
1" p: [: f. l. r7 Y
2
& j6 z! I8 k. W- t+ n6 p34 V9 B4 C! t# a, [$ C
45 ^2 ]( S. V$ [( [. f: o  O) B
5
6 i& {' ]% a  u2 m0 i69 W6 T" \- \0 q/ a! A  M
7& l! [8 j! `$ w
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配' U6 q6 E* @9 N: ]1 Y  B. K! X: `
s.str.contains('[f|g]at|n$')       # 二者等价: y* l8 G8 T5 r. c! \, M# `/ W
1 [; L$ \- S/ A
0    False+ k* O* i" x/ K2 J  x7 M9 Y
1     True
  B% }6 r8 G4 h) O6 I' N* X9 |" G: B2     True
0 O7 \7 P' W: F! q9 ~, g: wdtype: bool
& m" s$ z* v2 O1 G- {5 \$ K1
  B7 H8 X; h; y7 u8 n9 f4 R. Z; Z7 j20 g" i, b  ~/ y  ?" x: z/ v- H
3
: J3 m5 N; d4 ]0 I0 C* m# v6 E46 Q8 Y3 X+ t7 S  E) e6 E
5
( S- P# Y1 K. z6
2 Q$ B  r) p$ n9 p% M! o7
0 e5 {, g$ [: A8 P6 cstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:/ P# _0 X) L4 A/ v+ w& D
s = pd.Series(['This is an apple. That is not an apple.'])
( a$ i; _. y. ]5 s6 Y  Q2 }
/ D! q* U2 U# X1 g6 A5 js.str.find('apple')
2 I! X. H: p( }+ P9 JOut[62]:
, _" q/ A/ N) S9 _0    11! v6 C- u. h8 Q
dtype: int64# J  ^( s: Y. e- T0 e6 e3 P
; a2 ?! d' o8 _0 a
s.str.rfind('apple')9 A' A0 T# M3 m+ w) W9 S: Z( w/ a
Out[63]: ; y- q4 a4 k2 a( H; `9 d( f
0    33
2 l5 Y! Q# t+ N) c6 _) kdtype: int647 Y  U( {, U1 T" }) G
1+ @6 B+ T$ q3 k1 F; {
25 [6 N& P$ k5 F
3
- S+ U5 s! ~6 C: C* g+ u4
6 t( D) w9 r; Z, x/ J' H5
' }8 h4 h& r! z2 G2 e  j% N5 Q& A6
7 ~7 d: Y- F" l7 }! P7
8 p7 u7 K- G1 n. B: W5 b8# O+ T( j8 D% H& W) E
9
& h+ k  B3 Q! m/ U9 F, s10
# o2 \3 B7 U; b/ G  Y0 l4 d11
% u5 ^5 T' |9 S) g) U替换
, |. n/ L; F7 Z, _  zstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
8 n3 N/ J- j" J" `8 s, V8 T% |1 H% is = pd.Series(['a_1_b','c_?'])
1 T3 y0 W  W/ `0 [( c1 o( {# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?" @9 \' ?5 R+ l4 Y: ]3 O
s.str.replace('\d|\?', 'new', regex=True) 1 O/ ^  [( u& Y: P2 s$ ?1 C
% |  L. z7 Q  a7 i6 a6 z1 M3 i: C9 D
0    a_new_b
9 D6 D( J5 [) Y1      c_new
5 y2 z# O& S2 S3 ]$ ~. h# h: Rdtype: object+ n. L$ B2 n, l) w
1
' c+ P1 A! R6 ?! A! a23 s% n# q3 L/ j( i
3
' B) ~, Q, {% ?9 x' |8 z, V4* o- L" o. _" o* F8 ?; k- a5 N
5
% w7 `3 k, [/ N+ v6  x2 w6 {4 V7 }0 X7 o- W
7
/ q- d% o* d4 @/ x  [& q$ ~  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):- T7 \2 h' T  r- q# g4 w
* {( m! I. Q4 O* {; K# b8 o
s = pd.Series(['上海市黄浦区方浜中路249号',8 A# B; v% c% I, J4 M
                '上海市宝山区密山路5号',- x7 ]: p0 C$ k( e9 a8 @' Y
                '北京市昌平区北农路2号'])
4 }' Z6 c! y2 {& K- B2 Fpat = '(\w+市)(\w+区)(\w+路)(\d+号)', K( v- u* Y% R3 X8 v2 t
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
; z' \0 x5 C8 ~  I  @8 U$ _district = {'昌平区': 'CP District',
/ q( N1 w/ [1 i! l8 C. [            '黄浦区': 'HP District'," w$ l1 d: j% _+ ?+ g/ }1 q8 l
            '宝山区': 'BS District'}
$ n1 O, L5 M) j; E6 ^$ `' z! |$ Wroad = {'方浜中路': 'Mid Fangbin Road',
* m$ |; W' N6 M        '密山路': 'Mishan Road',# y5 _2 q+ q( \4 F/ T. D; W
        '北农路': 'Beinong Road'}  J6 B! E% W9 s( p/ w, g8 j/ V1 F( M+ k* n
def my_func(m):
6 H& l+ e3 s2 d; v    str_city = city[m.group(1)]
2 T  |$ C* O+ m5 c: O) r# H# T/ q7 I    str_district = district[m.group(2)]3 e- w1 @1 P. o- L
    str_road = road[m.group(3)]
0 T# E7 |+ v! _# N    str_no = 'No. ' + m.group(4)[:-1], s( b! @3 N3 F+ C4 J3 b9 z
    return ' '.join([str_city,7 h$ Z( X1 B( ]2 Y, S* _
                     str_district,
* k3 P3 D$ l$ c) j/ E% o2 I                     str_road,
! L, ]5 b5 f; v8 C. t" k                     str_no])
* `0 r3 R  R* E; _6 `s.str.replace(pat, my_func, regex=True)
) B& |- g9 J* ]* b8 W: k4 G/ @( n+ O; [; P
1
7 H* T9 T" Q3 o  a; I- G* m+ `2$ \( u% `! f9 C; k$ K  \
35 J( W3 P/ ?+ I
4
/ c' p# X* ~+ h/ y1 k' J0 A5
/ u/ l0 F3 f1 K7 u6* |% h9 R* e+ t
7
" a( H) T# O+ Y) t* P( D- `( w8
. @9 K/ R6 B( l8 t9
9 b+ z1 d( N9 f$ t1 c& L9 ~: q105 S3 {) w" t6 q, J9 q( H9 }5 A4 z
112 c: g& D" }* z; K9 k- n7 w3 H
122 Y' {4 y% X& ?: e
136 t, n5 q! F& }8 a
14& b+ ], h! y1 c; F- k) l
15
* {! S6 f, C/ \& t' p7 N: ~16
4 O2 g8 M5 X  u2 t17
- X8 X+ M" a8 @6 D2 k$ D18+ G: v' {* f( N  a
19- h, K8 H! @' B2 u" n% P8 H& Z
20
( \! G4 l3 s# @" u21; ]/ f  ~- l8 M% q# ^: T
0    Shanghai HP District Mid Fangbin Road No. 249
9 S. @  w% L3 t* i( R! z1 v# i1           Shanghai BS District Mishan Road No. 5
% j- F/ a/ }( _0 |/ @7 i2           Beijing CP District Beinong Road No. 2
' [7 I* ]' V) }  v. gdtype: object2 Q8 I; q' z; N1 F
1! a* l! {5 Q! e( h) h! Y$ L3 C
2
1 Y9 A! e" o: j/ \7 Y4 R: _- i2 x0 S3# j- _$ T. G, l8 m# w4 i
4
. v. |6 [( x' f这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
6 M  v% t) o5 y4 H3 k
# E$ Q9 _# o' k) x* H, F7 J  C# 将各个子组进行命名
; h2 {0 G: H( y& \5 ppat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
9 t7 m0 S" G$ c% x- G' sdef my_func(m):
5 e. b  T, w5 l" s    str_city = city[m.group('市名')]
: c# O: ~1 K% \8 N    str_district = district[m.group('区名')]
! K( X2 A: d- Q. ^    str_road = road[m.group('路名')]
. A* f  Z4 g! R) a    str_no = 'No. ' + m.group('编号')[:-1]
3 E) _9 m4 z, f7 x( k- e9 M7 g4 v    return ' '.join([str_city,* b7 U3 z; y* u# y
                     str_district,* V- }0 c1 j4 u6 B/ ~' N
                     str_road,, r# P/ J9 K6 M& O' F% h* u- ?
                     str_no])# I+ \( q" n) }3 h
s.str.replace(pat, my_func, regex=True)
8 R. \, O3 {7 X0 R  P8 ~$ K$ h14 {* |1 R# O% L/ z' Q
2) Q( x1 D/ f6 B; S2 X
3! l2 }$ W$ a- Q; O
4
0 y: n! P4 n  i3 I* N- d7 E5
5 Q" o4 L: @' G) Z: L6
6 y& ]. Y0 B5 s7
  A, B/ |' r9 L9 Y4 U; |+ c# N9 }8. [6 R) S4 ?4 v9 O, |
9
; ^/ _. x9 f+ B0 t: p10/ G# g) v# a1 E; z- ^# ~
11
& s0 p9 T1 _+ y12
0 Y! k4 |) K6 p7 U5 r4 r: \0    Shanghai HP District Mid Fangbin Road No. 249( d: t9 G: {& a2 R
1           Shanghai BS District Mishan Road No. 54 H* }6 v  ?$ z; W: A
2           Beijing CP District Beinong Road No. 2
: h% h# t& b0 D, k4 Ddtype: object
0 b8 l/ s: q4 E1
8 L7 ?7 _& C+ T' }0 c# @2
% T6 e! N! i8 C3 ]8 c3  T4 J6 `$ m, y6 |8 p' x0 d' J
4% p7 c1 E! I3 q; @! Z% [# m6 y$ v
  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。2 |% F/ C+ o* \  s- m

$ J7 t6 e" r% E. G& d$ z! q8.3.5 提取0 M/ z) p7 _0 k$ @
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
) D. [, o3 ~3 p9 i$ W' D% os.str.split('[市区路]')
) f& l! a% c5 w$ D4 IOut[43]:
6 i1 ^& R2 i7 Q* U0    [上海, 黄浦, 方浜中, 249号]5 P0 B, r; s" F9 [9 d1 H/ M% h
1       [上海, 宝山, 密山, 5号]5 ]  [; a5 l1 G  {# M4 X  u: |  n
dtype: object+ E9 J  V8 q0 v5 O+ U
/ h. b) {3 G6 X( h5 i) ^& B
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'3 ?3 T+ h9 D& Y0 e' q5 C/ b. j+ b
s.str.extract(pat)+ [8 r" D+ p. S  S# ^7 N& D
Out[78]:3 `" b$ Y& {$ W1 P$ O$ {
    0    1     2     3
( m1 n- L- P2 {( W0  上海市  黄浦区  方浜中路  249号4 f) E6 O" e, g# O* t, s+ J
1  上海市  宝山区   密山路    5号; o% P: a# h5 c8 n
2  北京市  昌平区   北农路    2号
) C4 [% ~7 c3 Y7 I7 [6 ?7 v) k  w1
7 k# a: o) e. v7 }' D23 L8 |$ t0 F/ c% d. H7 r/ D
38 k! |+ d6 y: `1 Q# v  J5 i
4
0 H* y+ q. P+ n  e# a5' [0 M9 Y' u+ E2 _/ w6 U( n
6
: a: M3 N& o) s, v/ U5 z0 W7
# Z5 L$ O6 }0 H4 {' o8, Q  r' K9 m& U' o' m/ W
9
/ Q+ X" B+ u+ h$ {% R9 S10- Y  n: g0 X2 B3 k
11
8 x% N$ B6 }4 h7 B' m4 h& _12
2 {* U! G) u) p+ r: N13/ j- q) y, \; [
通过子组的命名,可以直接对新生成DataFrame的列命名:' T: j* j) A% ^

% ^: ]8 O2 {% K, c5 j. u' Ipat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
1 }! `! _( k7 Z7 C# ~& Xs.str.extract(pat)" \3 R# G7 x/ S9 m( V
Out[79]:
# o7 Y3 n6 ]2 L    市名   区名    路名    编号
! u( b* k9 S* `" J5 f# z0  上海市  黄浦区  方浜中路  249号
  e+ i! Z# ~+ c9 d# l1  上海市  宝山区   密山路    5号
" Q& Q9 ]) Y) l. D6 m7 ?6 e2  北京市  昌平区   北农路    2号( H& r: B! k% D6 r9 F0 ?
1
! ~" M5 G2 x( R0 {0 K: u7 X( v5 Y2' R# C- R6 O1 @$ g, {- `% B4 P1 Q
3
' w  J3 Z& r) I6 ^/ L4* v9 n5 Q# h: a$ G, x8 F2 J
5
  T) x& w: V: m5 b  {6 Q63 u9 v- L2 I+ ^
75 G; x/ [$ g( e! @8 k
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
/ M2 G' ?) C3 I' s9 m8 Es = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
/ C1 y. M; k1 ^1 opat = '[A|B](\d+)[T|S](\d+)'
& r. r, q6 {' M! C- W5 zs.str.extractall(pat)
/ ]% |& f5 Q4 y) c* T/ I/ d# WOut[83]:
* [3 r! y7 O* u9 A" X       0   1# @5 K: W# J+ e4 \0 |! _" k4 h$ R
     match         ' G, J: x: }- i
my_A 0      135  15* S  k2 v+ v1 g
     1       26   5
& h7 [( Y( o! G( q% u' H. h$ x6 wmy_B 0      674   2* h& k0 i1 p5 e$ d5 f7 g
     1       25   6# ~, K( S1 O% P2 w" r2 C+ y+ S
1
0 b7 M; z7 L7 N& Y22 ~. [* o, R% j4 l- ^8 S
3
; K  q& C  j& t% K7 R  S4) E* z; |9 \& D  F/ ]4 D
5' q; l1 `7 s1 w* S0 ?' [- p
6' F0 A9 z7 W1 C$ H" V) |
7
8 r! T; z( ?! j' k; X* @! H8 `, d8
7 k" J: ^0 s' z5 }0 z: A/ X  p! y92 i9 C% v. {- s% |/ s' m0 J. b) L
10& i+ t8 B8 d0 S
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)': t% d1 n, V  x0 ]# w# o
s.str.extractall(pat_with_name), F- [  [: J! ^% f4 _5 q  L
Out[84]: " ^2 z$ T4 i+ i) [* _
           name1 name2$ w  G/ a" ?% n( B; o  o2 G
     match            
/ H+ X9 ^1 A: @+ a% O; y; c/ o# xmy_A 0       135    15
5 ]* ]* Q5 l+ d" C3 X( x8 T     1        26     57 _4 W: i" l* i4 l& k8 J
my_B 0       674     2( N( {% e5 m3 W9 b
     1        25     6( t# c  f! U  q& P8 z
1
" z0 [; u% J# @2
3 q% o* J9 L7 O3
. q1 [1 b: j& X; L4
  _; V& ^/ ~, |- c8 Y) ~2 g* v57 _$ D" N( K* [4 b4 Y- B
6
6 J4 l2 B* n$ r5 d7
6 a: D1 t$ m: C" S8
9 w7 }" u: b  l; p: Q* U9# h, |+ Y9 f( G0 w  G! q  A
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
9 N: m; C, K5 w; U% l8 C1 ms.str.findall(pat)
4 J) b0 W6 I8 g6 Z( V: G5 {' V+ Q. b1; G* Z7 z- y6 D% d$ w. N
my_A    [(135, 15), (26, 5)]+ T2 W7 K* |% q) T; P1 |+ l4 i- B. Z
my_B     [(674, 2), (25, 6)]
% i$ {2 Z6 ]# O8 B/ s" G) kdtype: object- W7 n) G! O. z
12 ?" t( q9 t! b$ B  o) [
2- s" c0 `! N4 l$ S
3
* t* V: }) _8 Q- s8.4、常用字符串函数
* R; y/ a/ c( o. d$ ^* P- ?; P& ^  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。8 ]9 j3 w5 H. S; A2 y/ F2 E
0 l9 c# I* ~% j+ L0 \% Q
8.4.1 字母型函数8 d8 a- V( _: P$ r$ `6 M
  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
- _9 N% g3 H" F. n, z
' E" Z+ v$ C% J. Ss = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])1 y  q2 r, _' n: b  i- Z6 Y

0 |% h3 e) r& m4 Us.str.upper()  J; P! ~! I3 C
Out[87]:
& w) C9 n8 d& v8 a/ ~( l  `) q- u! z5 W% V0                 LOWER
3 V& H7 l" I& ~  e9 C& U" `1              CAPITALS/ @6 V' V$ O- }$ w$ V) b2 W
2    THIS IS A SENTENCE  {8 ~( ]* b5 [5 P4 P% X
3              SWAPCASE& o" A3 Y' u6 P8 Z
dtype: object
% ?5 [( i# }& g! @' _# U  ]" }2 X* Y6 l
s.str.lower(): L8 x' }$ g3 i
Out[88]: + A; M, R: Y# X+ u
0                 lower3 D0 [6 y; c0 O5 Z0 U) t
1              capitals, c7 M. E! `5 m7 R( {# t) I8 m8 i1 W
2    this is a sentence
8 |2 B6 [0 Y' {3              swapcase+ O. ^+ [# \9 w+ `8 p" o& F
dtype: object" H9 H* P% X! O& H: c/ f' P  \
. u; S% t1 M; C
s.str.title()  # 首字母大写
& _; w+ L$ ^8 w$ B+ X: C. `Out[89]:
* i2 L  x2 H, g1 O0                 Lower7 \; P3 x/ X5 i$ k) e% d
1              Capitals
( c- S0 q$ y  H& u& s/ W2    This Is A Sentence
; ~* \6 R4 O) i3              Swapcase  l" C5 s  _+ w. S2 R
dtype: object
; p/ |0 [0 b4 e( l( h5 q& J' d5 q5 c  s) h3 I$ d" o6 l4 R8 n( J
s.str.capitalize()  # 句首大写
1 S( w- G" O# y7 X  P+ |Out[90]: 9 H7 I- X8 f; O3 U2 _9 R' L
0                 Lower9 d9 J) U" M4 Z. z% R* I! h4 u8 v7 \
1              Capitals
3 ~  x% @1 z2 T  m2 p6 w2    This is a sentence: H* `! P- O: A2 i0 p4 W
3              Swapcase7 D0 I+ V- `/ ]1 f4 A" l
dtype: object6 Y6 _5 ]" i2 V

) T+ G+ r" f$ d7 }+ Z; rs.str.swapcase() # 将大写转换为小写,将小写转换为大写。
0 K9 Z' {* ~4 v3 F" }- v- Q/ e8 ROut[91]:
4 `' e- r2 |2 \0 ~& v7 s6 q0                 LOWER3 x/ y6 v5 i6 p% z# W. }
1              capitals  N" h3 m8 p9 |# z+ f$ N' }9 T5 K
2    THIS IS A SENTENCE
- @9 m1 L8 t7 P9 y3              sWaPcAsE- e5 G+ ?# C, ?* `1 B; z4 a+ y
dtype: object* q* {7 p! N# O% I
$ B  I+ e  {8 W1 s9 k
s.str.casefold()  # 去除字符串中所有大小写区别
4 v/ G! F( R" h) m. H1 S" d9 M0 h9 [& `. r( A/ e/ c
0                 lower5 \9 M7 y; U% H9 I
1              capitals
2 @" k5 l. k  E, h0 \1 G1 R2    this is a sentence8 b+ w# |& u# l* {2 a( e2 S
3              swapcase
# z* v, m7 D- E  X) C; R1 T+ O2 z# a/ ?" O  n( J! I. `: W8 c. w
1
" P8 n0 t7 p0 |$ c2
/ n( A5 k9 p. {" A6 L1 s) g31 a1 q9 p! Q" q1 R  D
4( n" G4 {* s6 u/ I0 e8 f! |4 @
5& W5 U+ j, t8 D
6
  ~$ {1 y# u( w! j7, V" g+ {, D+ }
8
2 y% f3 j5 I  S1 P$ t9, l) a5 q" f* e6 Q: x( H$ w0 I
10- l6 ?& s% H3 [8 Q/ A
11
6 k) o( \7 ]3 a3 [) N6 j! C; I4 ^9 d& C12
  Q' `# h1 @2 Q; u7 p13
) O7 f" N- [/ T) h6 u9 W: S/ E14. ]( b* F% v& u' k4 e1 W- S+ W
15. h8 l* V) r, ^% U
16; b$ q# ?6 ~& b2 B
17* h4 U9 V- K, b) u* b. t* P5 {/ K# x
18
7 S8 A$ y6 d5 N, ~+ E199 E3 a0 [9 m6 m5 t
206 X, p, J: p, x! }8 N% T8 S1 Y2 k- u
21; D+ P7 j$ Z( B
22
* r+ ?, ?! m1 O7 n% T& u& `8 F3 \23- s) U* W' Q$ N
24
2 o; N7 m( `3 h. L, ?+ E* p. ?# D25
5 B, T4 }4 D% H# K* V26
5 q9 Y' `$ f& Z) T27
; F6 t) j3 b3 e. U% B! q! L) x9 u28
( k! M5 b# H3 T7 X% K% U29
0 L: `) g1 P6 }4 w. V1 j% S30
! s4 N* Q' u- ?$ {( @31
5 p8 v- z5 c; q3 X1 }4 Q( C/ @+ k32
6 g2 a# T) u( p* V% ~: I33
9 f- {1 v; N) x! j+ W; K34
, `/ r/ M$ e  u$ r" L, y35
8 y# K; ^! O7 ]) m* f) M, g2 j36( ]4 T8 C! U# W5 e9 G
37. M8 |4 ?1 S; I6 M
38
% W2 v' U5 a& @; }39
; L4 U( H6 V; U! Q40
) e$ @& x3 j1 Q; d6 L1 Z9 \41- m+ G1 s6 A9 A+ o
42. s& h& a$ V# @$ `3 P' \
43
9 M' o  ]$ z8 L: N5 i& ]: p- d$ f2 N44; \+ X8 H0 [. F) w! O5 w
45
! p2 [0 }/ k' e; b46& g2 K- _6 w: q/ e& ~, w
47
$ q. H) @- D: j5 w48
7 a- G' O: s- d2 }! S3 `- H8.4.2 数值型函数
( b6 z5 i3 h; K9 r5 ?, z) |- M  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
+ K0 W* |) ]8 Q4 B. _/ M+ t3 c8 `  j
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:! r' @% C! R# H
raise:直接报错,默认选项
" o- A/ E4 T$ A5 x& d& w3 Mcoerce:设为缺失值2 d4 Z" v9 R2 Z( b' R
ignore:保持原来的字符串。
/ g% i/ K; P' d. Y3 J) H. fdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
2 _' Z1 ^, y2 N+ o, [6 ls = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])6 x) ^0 m% j# i8 {

" f: O* t& t; X0 @! J9 S$ w% A' Rpd.to_numeric(s, errors='ignore')( g, `" M- J# O1 I
Out[93]: - ]8 R+ p4 A. S3 _: V+ W4 Z( x
0       1& `! H! ~+ Z; U
1     2.25 p) V+ g% h5 ?- Y3 e! b# U9 D
2      2e- g$ L' _* ~7 W. z
3      ??
) B4 F$ q4 c& W0 ^5 M4    -2.1* @  c& p, O$ i5 g4 x( O' k4 Q
5       0
8 f, |, j. K7 R; Q3 ?  \! @dtype: object
) X% s0 p" w3 F: r/ r. ]" p
3 `! H9 i3 ^6 k- C6 e$ S. opd.to_numeric(s, errors='coerce')& W/ p9 F4 {& r' ^: e& W% p
Out[94]:
" m' K! h# o7 g+ a, }; Z0    1.0
* l& t% K* q3 A0 ^# s- h% u1    2.2
2 H9 ?! H0 Q) O6 C2    NaN
2 u2 s0 y$ r& z3    NaN# H. f  d# L, h) I) V
4   -2.1& a: e" ~6 T9 ~0 W" g7 {8 Z+ ]4 @
5    0.0( Z2 V' d8 T& m- Z
dtype: float64
3 }: ^0 T9 K( P; \* }0 O2 r
( s+ N) m  E# Q. q2 s7 F1& o7 c% y- G( V% y; s
2
. R" ?. d9 z0 U7 w; I3 \3
3 N2 j- c! q4 c" W  g; I8 J9 Z4
* X, V8 ?4 C2 p/ S$ ^; L5- R) C8 v4 D% n) \5 l
6% b, a. S3 Y9 r: `
7' P/ n3 O4 d1 L$ Q
8
8 n- z3 c8 d' F( |+ b% W9
+ W3 X* ^8 h8 q; f10
* o' V" C# H& z) q11* O, F( Z& B, e3 i! v1 X
121 Q0 f8 ~9 W" j1 K0 F
13( X* g2 w7 N7 Q) [0 S. }* z
14# q: c) r% F& C' Y+ M' N
15% s4 B* }9 U8 g3 P. y0 Q
16
- i6 d2 G9 f# ?17. Q1 K9 l- S0 i& Q
18
  X0 k% m2 l3 k' J3 ?$ E* ~' K# S% G19+ Y; u3 U$ t0 U" C2 \7 p
20) U. t$ }, [1 o5 y
21
4 ?* Q# D7 ?) M; t% C  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:7 K) y4 F# h$ h+ ^8 W1 {
8 u( D5 v6 {7 I) p0 G4 p9 }
s[pd.to_numeric(s, errors='coerce').isna()]$ P7 E6 Y1 q. o  k3 J1 U' R
Out[95]:
4 g+ l* h& x( \4 W" ]2    2e
+ T; d3 F2 Z( c3    ??, C6 h% N! k: |# R3 j8 q' k  ^& F
dtype: object
, A  T7 w* Q- N$ z& T1 f18 l$ [$ K( y$ L! g* W
23 [5 c5 |# a; ~5 |/ c
3
6 r$ K7 j) \* ]  e9 B4
1 m, j2 C5 f  V  H4 _  v# M5
1 c" b- N* t' b! Z! u8.4.3 统计型函数
* X6 K4 g) S3 _' G  Z/ Z( M  count和len的作用分别是返回出现正则模式的次数和字符串的长度:
4 N: l( z# H8 o
2 g. D6 c" E1 |6 Z1 k+ A7 Is = pd.Series(['cat rat fat at', 'get feed sheet heat'])
, k* \* V3 R6 g: I! _2 \7 A; l9 H( F+ |( \$ B
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
; H. p- t- k  u, D0 O! ^- A$ uOut[97]:
1 k) G( M! o2 Z8 K0    2
( Q, L5 k# H# L; \. c( \% Y1    2  R/ g2 j4 s9 z) ^7 T  F
dtype: int642 ?: R. T5 ]6 B: S2 o

/ Y: Y0 o, t* p5 l, w- E& U2 ms.str.len(). _& p6 ]" \( i
Out[98]:
$ Y' P/ {6 S- Q0    141 Q$ d; ^! s+ n! j
1    19
% p* c; J& ~6 M8 j& g  _dtype: int64
# o2 t0 c  k8 H! s8 H8 O0 @1
) Q5 k* @) Y' x" R& R! A2
5 J( }1 q0 h: T) y6 S37 t: I% t; K* o0 y% u. J
4
. V" t1 D; o" W! ^' ~, z# e& q6 G! d5* V1 a" `- o* I6 d: F5 C" u
6* l" u" u( O" M  z: N
7
$ o4 H. v, v; w8 m( Q/ T- o- C+ p8
6 @( i7 s2 \; s94 ?  {! P7 d1 I7 e. G3 c2 i
10
1 s6 b2 @% c) K2 O$ Q9 e  ?11
7 F) ]" {; H" ~. G12
8 }' q: Z. K. W4 t+ M0 M; Q  I133 \: M- r5 t8 i% k5 A
8.4.4 格式型函数1 f! w$ R3 J9 _3 j* d; I
  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。( ^0 U+ m4 X6 O8 R. ]. r, i. L
7 C. A1 }5 l1 T4 R( z* h
my_index = pd.Index([' col1', 'col2 ', ' col3 '])$ E  i$ F# M# m# `/ J9 [7 ~) }5 j

0 L. i8 l/ {/ ^. ^( B5 ymy_index.str.strip().str.len()
" _' P7 W  }9 o+ z  EOut[100]: Int64Index([4, 4, 4], dtype='int64')% Y0 b; _3 z- N# N
/ n" Y: o! i0 G) y
my_index.str.rstrip().str.len()
) M! S2 }* N" [+ @( jOut[101]: Int64Index([5, 4, 5], dtype='int64')
1 r/ x- D# `/ @( G8 M
. F- e* z( h( r# t" qmy_index.str.lstrip().str.len()/ J( c+ q: w7 k' P$ f! e
Out[102]: Int64Index([4, 5, 5], dtype='int64')2 h  C' D, m: o% b5 Y
1: j7 S' F" k9 a
2
2 ]! L- g. }1 J# @+ g3
/ O, ~$ H+ e5 V* o" J6 u2 e2 o4" ?1 ^$ j4 f4 A# Z1 M3 i
5
( r1 q2 |& _4 n" o: g6
' U: o" |; o1 @4 V+ I78 }; H4 L5 A4 Z2 x! U
8! |/ p! p1 R" {( g% w) g) U
9
+ d1 M0 E2 i6 N4 H" a10
$ |  J- r5 X& _! e' o) G* T  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:6 v* k* a2 G6 ?, `2 {. q/ U

+ [+ }* b8 j0 X. F+ l, G( c! _8 l, _s = pd.Series(['a','b','c'])
5 E) R; {5 Y" F3 l% E! b' C- Z) V1 }7 h( X4 A0 z5 M
s.str.pad(5,'left','*')
. ^( r% y, \8 ]" W& |2 Y0 D  |: IOut[104]:
6 E' f, Q3 R8 z0    ****a
$ C- [/ z& j3 [. ]" B' v1    ****b
1 s* N- z4 t9 p2    ****c
2 Y& J) g' u3 E) g' Edtype: object
) x- V& b, P, B: k( \! c/ }) @. U! l( r- g2 ^
s.str.pad(5,'right','*')+ L" M" w; e8 r2 I: W6 A. H- v; Y
Out[105]: 0 T/ }) W1 E5 A" x
0    a****6 R% i2 Q1 ~) ^* S, Z, c& F
1    b****+ q3 ?) H" I3 L3 r2 j' n
2    c****
( Q2 p+ [. _0 I3 H% K' ?: ^dtype: object
& c0 R; l9 l. @% Q0 C- J+ [- ^: t4 d; H0 t3 b
s.str.pad(5,'both','*'): k" f! U+ Z5 F/ m' X6 g
Out[106]: / P# w9 z8 O1 F( z8 l1 R& _# \
0    **a**
3 E* r, q, T  G6 K  d% z1    **b**5 n: }! U1 X+ X- g" {+ ?2 g
2    **c**2 q) A+ p$ _) P3 S- J; N
dtype: object, g3 p: D* k3 n" ~. p8 [

  J2 @0 X/ j( f5 P- e8 F2 G$ e+ `7 I1
' t; F' Z) N1 F; h0 @8 f/ P  H0 v24 h  v3 r8 L1 p5 s, K
36 r- [4 @0 X7 n0 N
4, ^7 c. L) q# K) R9 o) t2 s# Y% Z
50 H6 j$ ~, o% H9 j$ V" w( G
6
& S6 R! ?: l- j7 V7 e( L* P# c$ R! N/ e7: Q) ^& [3 P0 W! @
8
3 [/ ^& x, m9 g: Y3 B6 g4 s90 W5 d& `, o8 f. _4 S7 z8 U7 O
10$ I! {0 G% l- F
11
) [7 J) y) b3 D- a. }12
3 V1 b& C7 `" Q; d$ J7 t" h# g130 [& O: j; r5 V. u* F
14
! l4 a7 d2 |- e& j3 h15
% D/ o4 a8 F) A/ e0 I) a' l16
$ h8 J6 i$ x1 e7 j7 i* }17, G/ V6 t9 c  `# D2 ~
18
; R$ M- e# C- Q( k( w( V19
% |7 ]+ v1 o4 _  E20
& Y+ ~/ }# i9 j21
8 C) K( R, K  y; ~% q* |22
) \$ ?8 E9 I2 F+ h3 X  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:8 [- V8 p3 w+ [- x
- z4 b, d/ o+ N2 y- l' b- u- @+ U
s.str.rjust(5, '*')
+ \; ~% d9 X4 C1 `! fOut[107]:
3 m3 _# Q+ o  u3 G& @! l/ o$ m0    ****a. W: w0 B/ ?; A: M
1    ****b% ]& j6 t3 ~4 R8 s
2    ****c6 a2 i- e) f6 \7 {
dtype: object
; k3 @: ?0 o( _% F3 U6 s2 Z
& O( e1 S# O  g7 {) t- [# us.str.ljust(5, '*')8 Q: H6 l. n' P1 M9 j
Out[108]:
8 r4 T! B2 }; d2 g: K" X+ q& B0    a****
5 i9 N) k9 `3 Q1    b****& @! p  n/ q  }4 ~
2    c****6 h: E$ n) a: x6 R$ I$ Y$ }
dtype: object& C- F: M, z  `$ g* [
6 G) _% S1 s% v! Z9 z
s.str.center(5, '*')! i: b6 _9 |+ C5 I" V) ]; g5 g8 P& S3 g
Out[109]: 6 O3 z+ G6 Q" t0 K) e6 o" W
0    **a**
: y$ ^/ \/ u5 X5 e! d1    **b**/ N/ L3 ]: D) E! N' X# e
2    **c**- Q/ f& @3 D: d, t
dtype: object& }' c$ [; U; X5 V& w

9 a, e1 c% d1 R6 a5 \1 F# s3 ^8 C11 q# c6 p, x' f  V( z
2* H. ]/ J! n* w2 ^3 \; ?* a
3% R: B9 I+ \8 P' E, s2 x; j  ]
40 |* {* H8 \3 d) |! y. N- A
5
6 W7 Z# @8 ]  t7 G. O0 }4 s. q6" S$ \7 r" ]$ o/ ~% z
7
7 j, b5 I! ~5 M$ Q8$ W5 \. _- w6 L6 ^4 c3 Q- f6 c5 p
99 T8 {! d: `& T
10
1 z! h/ W2 Q) B7 h& M, M11
0 S7 _+ O4 r/ `  Y* F8 D12, d% l2 X* z$ E5 j9 s& t' G3 z
13
  H' Y# x; }3 o# t8 E14, y7 `" k5 F- h+ M
15
  ~5 b! m( j. A# r16) P* u: X2 B6 w- U
17
2 y4 @; F; x) \3 e9 b* t184 V9 q( _( Q3 C" e* L
19
6 a7 Z, g5 u- I9 d6 P204 r9 Q; V4 ^) m/ J1 Z
  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。" i4 T5 U2 g. @9 w6 Y2 D4 H
1 m5 H2 o- ~1 o/ ~% q; C' t
s = pd.Series([7, 155, 303000]).astype('string')
6 R. |% R9 m  s, R* d+ q: @5 o9 h% u! R
s.str.pad(6,'left','0'). N9 d6 C$ s6 W! J
Out[111]: 6 Y5 m% {# i( s* `' u
0    000007
1 i/ y  k6 ~4 C1    000155
* W9 l+ |, x% s2    303000% j$ j4 y+ N' s& w
dtype: string- \$ [5 f3 y+ y5 [' l5 C) h. |! ~
( |1 V1 L* l( F1 R" J, d0 `
s.str.rjust(6,'0')4 M0 f+ z! o- _0 r4 g
Out[112]:
3 a2 D6 p4 G* u" \6 s4 c0    000007
4 I/ o0 V  [# o, T1    000155- w0 a; m+ J5 x/ e5 U+ X# d: A
2    303000! O$ z4 ?5 k4 H8 |
dtype: string
( b5 w0 k; {3 @/ b9 A5 o: w1 g
s.str.zfill(6)# q& @+ ^+ r+ g+ L
Out[113]: - v: ]4 \6 H+ m5 [, L
0    000007
5 D/ ^& g% O1 y" D% \  k4 E1    000155- N! k5 O# g- B  W# v
2    303000) E( U0 M4 C3 \% d; \7 b
dtype: string/ M" `. ~' p, t9 \
- ]% p/ L1 T/ r1 ?. W
1
7 k* A5 Y& x0 D& f$ P/ v! E* F" d2
9 S5 x$ n: h$ V4 R6 \$ @3) L! t' m( K: h7 G# C
4
* ?! J/ W6 i) W5 \5
# o. B5 {- Z/ o6
' ?/ M" P; I$ }7" Q, E9 |5 f) N( J0 b3 a" z7 Y5 D
8
2 H3 p- h$ y9 v8 D$ }/ r+ c94 a+ P7 L- f% q5 J6 O2 u! C- Y
10
; f* e9 Q8 R, @% X3 l112 Y# _! k2 N0 G7 D
12" z, U# \- s# s5 @7 v; a% n! v
135 ^4 h9 f/ v! i
14' e- l# X5 Y5 |3 A. d" T# L
151 w) o4 o6 H8 A9 v/ w
16$ J8 [. e. K3 t2 ]% j
17* i& l2 q" n; T$ g2 p" N
18- n* g% \/ A) ^
19
" l' F) N2 V1 ^* s0 J  u20
: S6 W0 W& y8 S' L3 q* c21
& N+ v! m+ w. K! l7 }2 D22
& ]1 I# T' S2 Y* `4 k; w8.5 练习4 m$ x/ Z$ |8 q7 ]( `. P- A$ e1 l
Ex1:房屋信息数据集
& q  L; p7 V3 Q; ^1 }1 T现有一份房屋信息数据集如下:
+ H( D6 Q" M: p3 X) h+ |' h- O9 f" Y7 F2 M& a) Y3 z
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])" g0 J6 c5 g5 y- ~: W& x1 L
df.head(3)3 g& f* {! d$ X4 H, E" _
Out[115]:
8 m6 L* L: @+ c, ~9 ]1 x3 j      floor    year    area price
- j( `- U* ^2 f4 z+ Q) I, L0   高层(共6层)  1986年建  58.23㎡  155万5 j( A. a5 ?+ O3 e- N/ v
1  中层(共20层)  2020年建     88㎡  155万
& i" D) A4 `& ]9 V2  低层(共28层)  2010年建  89.33㎡  365万1 B+ J3 N- p  t( k
1
4 Z5 m) q6 s$ e$ I2( ]4 O3 d/ m$ [" w) q1 a' ~
3
% k! J) Q( M1 H* p" ?$ i4
% {8 W3 m0 _2 A  |53 p+ E" e5 K  v& y+ F2 z& W
69 g3 }) \7 ]3 T3 U6 B6 j+ \1 \- h
7" \6 N9 u8 O3 j7 b9 V
将year列改为整数年份存储。
3 M! v0 C2 e% z4 U4 l将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
6 d$ P5 O* W6 b* i' C计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数7 s* G2 e2 E2 f1 m6 V; E
将year列改为整数年份存储。
9 i6 w+ F3 @7 l" L0 B8 F- ~$ \( F"""
) J* _) N2 O1 J+ x- p整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
3 d$ \* s, R# C0 l, R注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,% L0 V6 l/ x" _7 `* ]! {- o+ V
转成int后,序列还有缺失值所以,还是变成了object。
/ Z* l, T. t2 L. ^3 A而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
  V5 y- \& B; \/ V7 L! R- ]"""+ v, t' z2 ]+ X- \" |( f
df = df.convert_dtypes()" K6 G3 N! x! d
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
5 g2 @; a# B- Q* Ndf.loc[df.year.notna()]['year'].head()
3 T. E6 h' E8 X; o7 }; i0 E5 K. f" @4 U6 I
0        19867 d# Q7 `) D  V$ R
1        2020* a+ Y. u9 E" V) F% R1 Y
2        2010
* X( ~& q  U* k9 u3        2014
$ K8 j5 R3 O8 J4        2015
2 _5 J/ A( j* q4 B3 b: j1 zName: year, Length: 12850, dtype: Int64( m# O: b* t' I) a# ]+ r7 v% A

* l) w, b" n: _' l! `5 R% }: U1
2 l& G* x, _/ @" H: h2 L- b' R0 E2
( e* d, X3 E' e6 E  Y8 O/ U3! ^( U. ~' G3 A- _! t' h& X
4
( {2 l* A( m6 |/ D  B5
6 [) D/ \; N+ X( ~2 V6
+ z: R* v1 w" j7 b* B7  [# ^' b! ~8 s3 @+ m1 |% Y/ z) l
8/ X( D8 O5 g2 E
99 V( }) N, [8 _" }+ d& c0 R7 U. t
10  Z) X* E! D8 R) ]. T+ f0 O2 v
11/ q( @, h: e9 C$ G
12
5 U& b+ O  x2 p) b/ ^& n13
/ e: }* g$ B1 \& ~" U% X6 p14
6 Z) B2 q0 ]+ N' z) d15. x. |$ Z& |# K# Q* d) V
16
+ v. _' R0 {. y* I1 Q" P7 c' ^参考答案:
4 [  M, z7 x' C/ W% c7 m, p8 d
# p/ P" }9 `8 j7 L: ^. ^# M不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么' }, }0 N9 G" k2 ^; V
' g; E( O( H9 @0 D
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') 5 i% ^. i' A6 x  J1 l3 x: }
df.loc[df.year.notna()]['year']$ a" r, k3 q9 U1 M
1' I4 d4 z% y7 r: E. v
2
7 s2 G8 M4 X/ k6 e将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。% |% [4 C9 v4 r  Z! z8 _
pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'7 A* x" }% |4 Z+ {3 l
df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次! {' W1 s9 ?1 k# \; M( T
df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
8 L6 }) j; N7 v( q1 l/ qdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
- @; `$ D5 y' H2 w5 qdf=df[['Level','Highest','year','area','price']]
% t/ i2 l4 J# i+ d( ddf.head()/ d# y8 N$ I2 s0 X7 ~9 r# c% m% e

  L/ ~" ]  n8 M0 {& |   Level  Highest        year        area        price0 w) P5 I7 K* p# X% f' i) x
0        高层                6                1986        58.23㎡        155万
6 I8 [' {' g. ?4 e2 n# a7 G1        中层                20                2020        88㎡        155万
( s/ M2 w+ D/ B9 }& p  G) z6 Q* {4 |2        低层                28                2010        89.33㎡        365万
6 G+ ]* F! b4 d/ Y0 Z1 P7 G* G2 C3        低层                20                2014        82㎡        308万- S4 O7 a( g- M: m; X
4        高层                1                2015        98㎡        117万
  \) C9 h6 L( H8 {, H( [! q: T1
* o/ o0 G$ x% a' p$ v) Y2
' [2 A: p9 O5 A3
& v- E/ C% @8 |" U) r/ X49 C2 }% U0 o0 V* L+ l
50 u; }( C5 o' Y& J
6
! W, e( [. ]; _; `7
7 n3 B+ U& T3 }8
% v( V+ n8 M6 J" H93 @! G3 [1 l6 Z( E
104 a, _5 @) z9 h' R# ^# b
11
- f( F  e2 n. ^8 c12/ h7 Y3 r0 Z) A
13* p  P9 i3 v% R
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
  r% C* ]2 g: P! Wpat = '(\w层)(共(\d+)层)'
4 X. a6 a7 W$ ~  E$ snew_cols = df.floor.str.extract(pat).rename(+ ~0 e! i* I; k; D4 M4 N
                    columns={0:'Level', 1:'Highest'})! l  C2 d3 C6 ^
1 ]% L( B% W' u- I
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)0 D; D4 j/ j  h, X
df.head(3). k1 T8 ^5 v- y7 V+ _9 h
8 P+ S# Q( n' d, C- {2 n6 a
Out[163]:
  q$ G/ D6 C7 B( T" }   year    area price    Level Highest7 X' f$ c. E: M
0  1986  58.23㎡  155万    高层       6; M0 K6 V* ~: [1 M' k6 d
1  2020     88㎡  155万    中层      20, ]% m7 I8 \2 H( L2 ?
2  2010  89.33㎡  365万    低层      28
7 ]* R+ b6 `! _0 [, b1
; \  P4 i  D: W2
  E; r3 }1 n+ l! y9 J) G+ O3
; v/ r4 Q( F; r* C, O  u- A4% `6 L6 ]. ]: j8 v! g9 X9 X0 I
5' [3 T& L- z3 Z9 F( R
6% }4 ^9 I! p6 G9 B% L/ x3 y. ^4 u
7* n; b. m3 V/ s2 v1 R7 o
8
/ K/ L/ b/ i6 I) v4 |& f, r93 L# D6 L, H! p5 g, |9 H
10
, u' W6 V  l' e, x112 i: [/ P* \+ g5 w; ]" I! j
12
) p- o9 J4 w% Y- W& l13
# D2 @6 z) A5 k计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
2 ], h8 t5 E* E3 Y1 N/ [9 P; ]"""
% D, |3 m( T1 W9 T# ~& J: Q! b8 C. Kstr.findall返回的结果都是列表,只能用apply取值去掉列表形式
* y  B1 Q% o, A/ h1 z3 ~# m5 c参考答案用pd.to_numeric(df.area.str[:-1])更简洁) C* w: G# @1 R# F2 u% W6 G2 |, _1 N( P
由于area和price都没有缺失值,所以可以直接转类型& @& K7 e3 r9 @- `+ [
"""4 B0 w) x9 e( U1 ~$ H' l
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
/ f4 v2 B5 H% t) ~8 [9 ^$ s7 Ydf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
: Q/ W$ K* S9 r- o; p: fdf.eval('avg_price=10000*new_price/new_area',inplace=True)  [/ o$ z* U6 G# `0 @
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
; y! U! ?  Y' i1 c* F9 \% o. D! I# 最后数字+元/平米写法更简单. m% J0 P8 r" n. Y8 O
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
0 m! y5 P$ v. k, v5 Odel df['new_area'],df['new_price']( M7 r$ I6 o3 L4 L/ _% [9 {
df.head()$ ]4 J1 V9 k7 B

/ S5 U% m; \- T3 E5 J, ^   Level        Highest        year        area        price        avg_price
! X# }1 J; m( ]' o; V' n2 r* t% b0        高层                        6        1986        58.23㎡        155万        26618元/平米8 }5 D7 z* q! K
1        中层                        20        2020        88㎡        155万        17613元/平米4 c- a& a3 n3 ~& |3 w0 `% x
2        低层                        28        2010        89.33㎡        365万        40859元/平米
# p2 k9 [! w" e7 k8 \; f' W2 _! \0 d3        低层                        20        2014        82㎡        308万        37560元/平米
' w( U1 u2 L; t; S- w* \9 S2 q/ l' F4        高层                        1        2015        98㎡        117万        11938元/平米( e* o' y! x" ?0 p: w( B; R( ~
$ l- p" w  p5 V8 u9 u
17 v0 ?; H% Y% X- |* p" ]
2
# p" q5 I: h. p4 O5 B, v) q31 z8 ]& _5 X2 C3 R
4
2 K8 A) Q) j! @, F0 l5
* f, @5 v; m' N5 |6
( j! y1 {+ E  A- O% o$ ]7
6 `# r" T! X9 {  y8
; |( U7 b0 K$ f5 e! ]  i% X; K  U9  A% {% D5 T2 l% v
10
7 I" O9 f+ T3 I0 H6 m( \11
: w& Z, B7 h! L3 ^$ H12% A) n; \4 Q# r% l
13
8 w7 T: Z2 b  V: n) F8 V- E14( M. T5 J/ }9 y: ]8 S
158 z6 l5 W% k; W+ p2 M
162 ^# ^. c2 M0 c/ h/ ^' L
17
' {4 K& v1 ^1 O% Q4 b18) q2 D! w3 t1 i4 @( x
19  P) F& d% @: W2 t
208 N" ]3 E# M/ I) c/ x
# 参考答案
+ T1 ~- T* d8 }  [2 Fs_area = pd.to_numeric(df.area.str[:-1])* b/ ~/ ], T9 S, n
s_price = pd.to_numeric(df.price.str[:-1])
) ]$ P5 d7 o+ k; w, ]% p( x0 |df['avg_price'] = ((s_price/s_area)*10000).astype(
" F  ^* j  |6 I" i2 N2 a4 z) G                    'int').astype('string') + '元/平米'
& _& G' z- z! L& F, p4 Z# [5 r8 `) d1 I0 U) K+ J, w7 h, J! `
df.head(3)
2 f1 a$ _  \" {: OOut[167]:
. d+ z, _4 x, e/ c6 X$ w   year    area   price   Level Highest  avg_price
2 C) P' g% D7 @0  1986  58.23㎡  155万    高层     6          26618元/平米: _6 ^- \& b6 q- A9 {" b, t
1  2020     88㎡  155万    中层     20          17613元/平米
9 e5 s0 V- L% p. U9 I2  2010  89.33㎡  365万    低层     28          40859元/平米
" P$ }) F& T; a( _# {0 c- M, i/ I1 |" p1
. F! v$ b" E& K+ m/ j4 o( x2
3 B1 J. I7 V  g+ w  h35 }: Z0 s7 D. _: e+ y
4
0 I6 n3 l: Y) k( S2 ]% B* Q5
! r+ M! g, B+ X3 D# S/ B6
6 S6 d+ u/ v+ |: N- ~7* G$ n9 Y9 G: o/ M/ j0 R! \0 U! h9 |
8* U+ F; m4 x5 z9 i$ @
9' G" k, b7 e! [5 s0 N, p3 Z
105 ^8 r  Q/ R, ~' S- B3 e2 A# J6 \
11
  s+ h0 D. p8 H0 l- b, `- Y4 ?12
, G, I3 l, p/ s3 vEx2:《权力的游戏》剧本数据集
% I/ V4 ]! Y% K. }7 \- i现有一份权力的游戏剧本数据集如下:" B; F# }+ o1 S5 j; _: U

3 \' O: a; ?! |& Q! j+ ~  {df = pd.read_csv('../data/script.csv')
; s& k3 v, i3 J/ H/ gdf.head(3)# q1 _0 @; Q; |) J+ B- g

4 P, C) `& j4 n! G# xOut[115]: 1 k% E) U' n7 c6 k6 U8 p# V* \
Out[117]: ! p+ a; t8 _0 V  e- d1 r
  Release Date    Season   Episode      Episode Title          Name                                           Sentence
& _& O$ j& h# N: i' o  v2 k# u  G0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
. i* W. I" v2 ]/ ]: z( ~1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...( @/ G" S* o: J9 t0 h: t0 x' `$ r
2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce   r. o, `' J6 X- V" _
1& E( {& N& A, y6 W9 Y- C$ \& W" [
2, Z* M  u$ Z( ]+ x
31 a  _+ E6 {/ e+ B
48 s# L! z- {7 g. `3 d) Z7 ?- X3 L6 f
5
  `2 ^9 x+ \# m: ^5 U6
- s2 P; v# @/ T+ b$ J* [0 Z; m7; C4 \0 q! M. P# q# h" s. `) j
8# _2 E; c3 P! }' m- ]2 ]& b- @0 P
98 Q7 d7 I6 q5 f6 T1 H1 o
计算每一个Episode的台词条数。+ U; k2 r  ~- A" A+ T+ X8 W- ^
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
: h; f4 g! D! T4 o! g8 m- j若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。" G5 f, P1 @2 w) Y
计算每一个Episode的台词条数。
; @* y2 C9 B+ V9 @# f1 b" K2 I2 fdf.columns =df.columns.str.strip() #  列名中有空格
$ C/ g- ~  s0 {- S1 r& _" Z* [$ Ddf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
' ?4 b9 _/ i% m: I0 i
, Z7 e$ I3 `) Iseason    Episode  
: ~0 F# z2 ]$ N( h7 [3 oSeason 7  Episode 5    505! t8 n6 [! y  K
Season 3  Episode 2    480! l' t8 I! K8 x6 q/ i* e
Season 4  Episode 1    475; d9 h9 T8 ?2 Y0 F+ B. E  Z" M* N
Season 3  Episode 5    4402 Z% H# A8 V: I  c: ^9 v, H
Season 2  Episode 2    432
5 k8 X+ H- ?5 L* p1
7 I% B, e6 K- M2 z% U7 p% s2
  Q2 k) a1 q, T" y! f' x2 G$ o3. \4 k. ]2 Q( B' K
4
7 K" j# ?" l9 D  D7 e6 t/ E5
3 X& T: Z0 p0 X+ u  y2 a( n6' z8 u5 W, d- c2 l1 q
78 o. j: {' r* ?
8
4 J+ d. n* w8 @+ j- r3 q9" B2 X# C: b- Y1 I
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。; G, C5 X3 i4 z; {  K6 B0 T2 ?5 @
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
( j2 e" x$ ^8 e4 G7 a7 Pdf['len_words']=df['Sentence'].str.count(r' ')+1* u! o1 ], C& V
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()/ L' ?# K- C6 l# O1 Y

. Q& K% B- i( q$ x% {Name0 W, x% ^- w; A2 y$ O
male singer          109.000000
6 Z5 {4 {1 |  E( ^6 r2 p; eslave owner           77.000000) F& U1 e0 H: w
manderly              62.000000; k/ k  B9 \' ~% T8 w' K
lollys stokeworth     62.0000008 Y, y( b! x  G2 v' X+ ^
dothraki matron       56.666667& s, @# x$ z2 x: ^/ F, z" s' L
Name: len_words, dtype: float649 d1 f- u* l+ h
1
8 G6 M9 g7 E2 l4 R; |- v+ ?25 ~! [% g$ N2 c, {6 Q
3& m5 M0 g* h4 o. j& |
4
& Q, N1 v* ?0 y! X2 T1 W5 b5
6 I( F- S& C& v8 N- n% E5 R6
$ n* M- J* ^$ q$ j  @79 A) s' l5 x- P. A9 l/ m& ]. F( G/ A
8
2 X& L, Y7 I+ e( i9 N8 E- @97 Y. ?5 S& ]3 I  O# g6 L$ M
10
( Y. V* X' x# T3 w6 R) P* |" k8 g/ b11
9 L% ]% `% F/ ]% y若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。; r2 _* m' `' e2 H9 V
df['Sentence'].str.count(r'\?') #  计算每人提问数
% i8 `8 X2 s8 f7 v. [ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
$ Y* L0 F+ U6 i  b3 j( G1 d1 pdel ls[23911] # 末行删去2 F& n/ ~2 g2 U2 z" `  c5 i
df['len_questions']=ls# {( }6 [; H/ d2 v9 X
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
0 u$ U1 U" E: H+ w7 F* H) M4 ~" w$ T/ E
Name  y* _5 m; L5 S9 Q4 l
tyrion lannister    527
! s2 _4 j5 [8 ajon snow            374
$ _3 _; q! Y: j3 ?2 Ljaime lannister     283; t; O+ O, |1 n6 U" A1 L0 X
arya stark          265( U) i* i8 T* _4 H+ Z3 i7 G5 g
cersei lannister    246
, |$ s, k* u  q7 `Name: len_questions, dtype: int64
7 |/ W5 r2 T) M1 O( `8 l0 [# z) d" y; g+ A3 P1 p4 E
# 参考答案  c7 T) t. v$ x& Y, t9 w) a5 B
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
7 l) w' D, R+ T5 a- O7 qs.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()9 ~0 X. i! ~. t2 i) J) u$ s: x
; U7 d% m* Y7 q2 g2 L
1
2 c7 K6 i7 w( S9 f/ l, V0 x' Z2" q$ ]! o6 t/ C
3
1 }  N+ V/ z( ~4 m4 [4
# S! w* m4 y8 ^& k. m" ?9 \5
: B3 J2 @8 |1 z8 X+ D/ U7 z2 B# I1 T6
, {' m+ T6 h# G1 o3 P! p7
7 Z0 E) G4 ]0 Q& i5 j8
# a: M, @9 F4 i' P  d' ~- B5 v9: A4 F- D3 Y& Z% g
10
% S1 ^1 F/ j* d: O4 c5 u) {7 j3 q1 L11. u" u5 s2 I# c( u9 G! N
12
) w$ h+ e/ z6 o9 D) T137 h' x: f7 ]/ m& t! X
14+ E8 g7 |# j& {
15
- c+ A) [9 j  |$ \) ^/ B: _16& S$ ~* [6 D  B
17: V! d4 }" ]+ E5 o
第九章 分类数据
2 d1 q$ J+ e0 l( D3 G) @' ?import numpy as np
+ Y1 S+ g5 b7 a$ J2 i! g' g0 B, C9 fimport pandas as pd, z& E' n7 Q. A
1; Z5 M7 N$ h. W) C/ Y
2
* c' w- |7 w1 q8 i9.1 cat对象/ ^1 ]+ }3 s* V  a# @  F) l
9.1.1 cat对象的属性  d5 Q- i, a2 a0 f& t
  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
% e7 X& |2 H- b; ~0 [) H% J; c( \$ X/ J
% k2 y  W3 H* m3 ~2 i( |df = pd.read_csv('data/learn_pandas.csv',
; b4 B* z. f+ c" p9 y2 k3 e. V     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
# ^4 A) i: w+ ^( J2 u: @  r: d/ qs = df.Grade.astype('category')- ^9 z0 H. w( L4 G, {

9 {8 C# s. @4 ?, us.head()
- A2 U+ U/ U* w% F: f/ E* V2 C  u+ T3 cOut[5]:
8 ?3 f- |' W; {; h# P: {0     Freshman
1 V% r7 T1 B2 p; j* M' \% N1     Freshman
( A( y( L2 p& f5 X( M& W2       Senior, f) H. v, d1 C! B5 s% i
3    Sophomore  S1 \% G: n; m7 A1 ?3 `' |: X  T
4    Sophomore4 U$ Y  B# T  w$ l& i
Name: Grade, dtype: category
& Q! b6 R9 Y% Q' s) q- p7 J. C& eCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
% d; s0 L; W% Q" P$ k) F  s3 H1% o8 T) x2 B+ s- M- z# D! f
2# C% L/ a, v6 c+ e5 ]) t
3# x% s) A' n$ u  k  X
4' I2 j* C  |! A% M9 Q7 }
5' L% V) b8 `, v3 d
6
. x) J2 z  A" B; {% @5 q7! \/ b  Y3 l. ^) y! y2 d
8
2 w5 ^% t8 O. H! Y! T: p7 ~4 G4 r9
# l7 S1 B1 `1 w) H- C* H( p% l10
  J( X% n2 S! P/ V; D11  }0 u! ?' B6 d
124 @- n2 R* i. U/ w5 N
13
$ a+ r) H5 p" }9 C- }% r  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。. I1 I& p; d0 w4 }; N8 p
6 |  W( O: Y9 t1 {* q) v8 Y
s.cat; Q3 E8 G* T) m" F; S& m. k& J' E
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>  U6 Y* J, H. ^% `* l) v
1
- A! X4 n  k$ T7 B- i7 v2
  S  G/ ~# f$ }% Z0 qcat的属性:! G0 d5 q! a- K! V0 v- t2 X
3 Z5 [6 n' z* k2 L3 E
cat.categories:查看类别的本身,它以Index类型存储
! U% \) {+ q7 T* N9 \cat.ordered:类别是否有序
0 ]6 V) U" m. e; N- o6 [3 j3 kcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序1 w, h' I5 x. ?0 U: K
s.cat.categories
, Q3 `- `% V+ g+ c; q* MOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
/ g% ~" o& V3 W6 @  S# S0 X
$ E5 p$ R# `# q9 Rs.cat.ordered& e' i  C& c; S
Out[8]: False
) r: v" r! L* Q8 }/ E
! y; ^/ g2 O3 Us.cat.codes.head()  _7 j/ \5 f% g7 G0 b& d
Out[9]:
: J. c- C7 H1 C5 Q! R0    0
/ t: ~1 o7 f, q3 E1    0% f# [* w: z( v5 T! ?/ m2 q
2    27 p5 b# R+ Z- _! [6 `
3    3$ [/ X6 X8 Z% R# y" i1 |
4    3* C* C1 U0 U! J5 E& N$ |1 x4 T
dtype: int8
1 F! W( j6 A' _. U" C11 O% x- C. o: V0 p1 W8 D
2
8 U6 e* ~9 z5 k3. w. L- q# ^$ L! `
4# N/ V. b& c9 n& M& c- }2 X
5
  F+ l/ G9 y. L( I6% r0 S" m; J) ]. Y" \% r& x
7
# M1 Q$ R4 q6 a5 e3 o85 a; n/ ?0 ~$ Q: |
9& q, X% j; F5 U" n7 U; ~  z
10
4 e: s* Q& {& a8 j3 Q11
: U# F# f; C* l- @/ D- E% }5 X1 D12, Q! W3 O' }# I7 @3 K  ?
13
1 e& d! _' j; B+ J& z9 {14% D, \9 W, K0 S6 d. {2 s
9.1.2 类别的增加、删除和修改1 }4 w) T( J3 X! S9 j
  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?1 Y' X; o: H: @) L+ w% \  b

1 R; z6 I/ p2 i/ q; L$ _# e【NOTE】类别不得直接修改; z* {. E3 c/ @, S/ F( k$ E
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
7 }$ l- t: `3 F# O  e
2 j1 _" B1 H% Badd_categories:增加类别
* a8 m% K1 W/ X; qs = s.cat.add_categories('Graduate') # 增加一个毕业生类别1 W8 y4 w7 ~4 L% ^% d  G# B2 [8 p
s.cat.categories
, j$ l/ c# O  Y1 b. e
6 L9 k% e3 l+ NIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
% v8 e9 v6 U/ _( m' @( {$ @1" `9 F5 C# A& t0 D) F8 _
28 O5 Y+ g7 f2 I% `) D
38 x- I, N6 O) Q& \2 u  Q! R+ s4 w# ~
4
5 i- e" d) O, a7 p: Lremove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。+ [7 r: S7 U( \0 M4 ]3 N
s = s.cat.remove_categories('Freshman')
% R; W. @+ Q4 U% C( Z0 l' S8 O6 H& Z' v. }/ g3 d
s.cat.categories
$ x# F5 f: L( |1 B+ JOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')4 B$ e6 H$ y: `0 O( J( H
& \2 n3 s0 N- @( y& G
s.head()
! t* l) H" w4 \; C/ y4 KOut[14]:
5 L8 x4 ~2 j- b0          NaN
4 k: h9 M% D8 z1 B/ n" i% |* m1          NaN) e2 F8 q6 u; N. ~8 [" T
2       Senior
) V5 B+ x+ J  W0 g- l3    Sophomore
, ]* R$ h4 p3 R2 c! }, @* F; b4    Sophomore! |7 B! F2 Z! ~" v9 Y( ^
Name: Grade, dtype: category3 z! |1 Q3 ^! d6 L* [2 h0 d' R
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
0 ^* i$ N; i/ J! X% x4 t3 Y1
& K0 y- o% P  u) H* J" G2
9 H6 G$ E/ C# E3 x$ ?3- f0 u1 O. W  ?8 _
40 v# R8 z: ?6 V6 N2 n
5
! C, D& ^( H0 D9 K8 b6
1 }0 a4 C$ P. O  ?7
& M! m2 K4 |$ j' X' n8
& D" ?, @+ u: p9 d  ?1 V5 a9
& b0 _' }; Q, G: Z: s! L% P* [10
0 E8 n) A' z) p& o111 B1 T! B: C3 g, @: `% b+ O1 l
12
4 Y. a# r$ r0 L0 ]# j& v+ ?13
) k/ F% y- i2 y8 N: b- A14- N* [! n" c8 T1 `: v
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。5 p4 ^7 u+ X) P& a% b
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
$ @6 D( p) u& Z% Es.cat.categories
8 {, I  g0 K6 TOut[16]: Index(['Sophomore', 'PhD'], dtype='object')
* O$ j" @/ ]  C( S8 [4 A" E) H0 L6 ]1 J3 B6 V1 M' j
s.head()5 }: `1 N2 u8 S' q+ R) u
Out[17]:
# [0 {' f* ?2 n3 l* |. i0          NaN
# Y2 P* U/ T. x* {$ f4 F1          NaN$ x4 p3 B% C- Q- F9 {. J
2          NaN
# j7 U/ O) p/ V4 S3    Sophomore
9 Z0 C* b$ `" o6 A4    Sophomore3 R6 L* Q- q; ~8 K" E+ |" h
Name: Grade, dtype: category; s& e8 V7 f; d9 N
Categories (2, object): ['Sophomore', 'PhD']
. i  B9 g" z3 n4 M18 K5 k4 a/ h6 k& n" U6 h+ ~) z
2
& t2 U) g1 r/ X" K* ^  p: D3; ~6 f1 [$ t2 g8 t/ R0 o
4
2 g1 U9 Y! N, e8 p5 o. ~% j" ]5
- ]- J, L* O8 }4 a; i- b4 U1 q6+ P0 u. ?& V, s9 P0 L
7) @; {0 ~2 X3 ]3 @/ H- [
80 i1 F' w2 Y( h, f8 d
9, d: P% K* j7 v3 d
10+ @/ a' y) {6 I+ T
11" @. R; V$ s. b- F3 d5 {7 b
12) h7 _3 G! R: S7 S' Y/ E4 Z- }/ H: i
13: l, W' v, u! i, Q# s
remove_unused_categories:删除未出现在序列中的类别
. ]! p$ ~! I' u: H' ks = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
" Y2 L2 l# @" z5 a1 }2 ws.cat.categories
( P2 |1 ]% ]) i7 o7 \5 k- `  W+ F* p
* `, ?2 D% k( h- I$ zIndex(['Sophomore'], dtype='object')( B' R9 T% |7 i
1
3 e; J4 n/ _, V2- |2 v7 |0 D& m' F. k
38 F0 \6 x, s4 B& n& a
4
6 Y1 U6 M2 k% w; I7 b0 }! Srename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
( i1 ^! i0 ~4 r0 A2 w, Js = s.cat.rename_categories({'Sophomore':'本科二年级学生'})" n$ m( M2 a! S" @4 w" G4 s
s.head()4 x. I* U) O9 I: U$ o6 {) r
4 m2 E% A& h5 j1 u( C  C* c7 T
0        NaN. T* Y  k/ B  @0 Q  ~
1        NaN
( ^  W( ?+ I" ?0 L# d! a2        NaN" j, M; X6 \. R2 Y% i3 Z
3    本科二年级学生$ F! \5 y! k+ \1 V  Z( q1 {
4    本科二年级学生/ e, D/ U9 r; k) x, B
Name: Grade, dtype: category
" R' V5 O  c* B/ Q! ]/ HCategories (1, object): ['本科二年级学生']7 M2 r3 {0 _4 c6 ]9 t' Q& ]
1+ }+ f( g6 d3 [8 W7 ~( P
2% D) ~% r1 J6 K0 O$ C- M5 U0 `$ {
38 W6 {+ E: x' A1 |7 X
4
1 c) [3 r+ ~* y/ y5
9 f* D9 T& g' {2 J" i6( r, N9 O( c. c" J9 }
7
) z$ k& u- b, N8$ \& U( X9 J3 z; i
9
. R9 g" v  b( G) ?1 ~3 }9 s( Q10
. x2 ~  W/ E2 W  Y- `& H9.2 有序分类
9 Z, x, i/ P. V9.2.1 序的建立
! ~% d+ @9 _' G6 i0 s  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:0 y2 A7 }! J$ |

1 r! m# R% S  b! cs = df.Grade.astype('category')
! z7 w  X) `; |0 q/ ps = s.cat.reorder_categories(['Freshman', 'Sophomore',1 h& v# W) F1 v) n  S6 n$ P
                              'Junior', 'Senior'],ordered=True)
' |+ A5 \1 R( b& \' A( y& {s.head()
4 `8 ^  e, t; w# h  jOut[24]:
! j* V# ]1 b& F( S- t3 x/ G% F1 D0     Freshman
0 p7 O9 A) J" Z. v1     Freshman, I6 ?" g7 K8 N0 T# [( r+ f$ i  ?
2       Senior. }9 s3 G9 F& Q5 A2 w; j* h
3    Sophomore
9 ]: j- A3 b' B, T1 M4    Sophomore% i8 {' b, {  D" A
Name: Grade, dtype: category0 a7 Y1 t" U' @3 R: \% G5 ?4 \
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
9 @. F1 `; i( r/ R7 z) G; M
1 Z& }6 t9 ^7 o6 g5 K4 K7 @2 i  hs.cat.as_unordered().head()- {2 X0 L4 w( Q+ o' ]9 l
Out[25]: 9 v& c2 m" j1 x( @) a; w
0     Freshman
/ E, u0 w- ~' p# M' N1     Freshman! e1 M3 \  J6 |3 \
2       Senior: V1 U6 e, l; c; W8 Q0 b+ j
3    Sophomore" r' h, v1 t- w- F& I
4    Sophomore- I& @/ \' B; Q6 l: }
Name: Grade, dtype: category
/ T* r  R" R) n5 jCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
3 Q7 u5 x8 z5 R, F  z$ s$ {% I% P& @4 d& J' }4 d
10 Q0 m! {1 R( e0 T" Y4 h
2
$ A7 j% _, U/ F7 L% \" [3 t3' X: X8 ^! }9 S5 @% Y# M- E5 z5 g
4
1 g1 Z: M0 j0 }8 |58 D. O  i* P+ x0 a
6  _& {. |% s% j' m- j$ K
7, z% l3 ~4 u1 _* f) m0 L
8' ]" h) \  F) S2 Z
9+ @$ @" b1 V1 b, ?; R" {
10
7 a7 [% H! j) n) U11
% [( j. w* j' p12, ~$ Z5 p7 }+ ]# n1 j
13; u1 ~( A, E) }- q% u3 e3 |/ i
14" l& G) d0 ~* G7 L
15& v( K- ^8 [# r4 y/ W9 }
16
. T6 O" f1 V0 S! U6 [6 L176 h, y: Q# ?6 V+ I$ b+ c  m
18
6 J/ K- d2 O7 H( j4 F6 M, i; X195 Y" O! J' S4 C( d/ A; A4 D+ K
20
$ M; n3 k& u, v* k2 o: B, Q21
# Q$ i5 D  x# F22& b) ?5 w7 r( I; Y0 o, N  D' f8 P# @
  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。5 M: D( w& p: A% [7 h

5 C: g3 M/ ^; ^( X$ g9.2.2 排序和比较
) e( S8 h( n9 S3 y& C, O$ J在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。' Q6 g6 R* `8 p9 {2 o

% E8 D  p* Q7 P( U4 T; \  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
  q; }  G& Z5 M% H; L2 o
) C( V) J+ Q5 Z2 }( l/ gdf.Grade = df.Grade.astype('category')
* c; G. T) e; |& ^' P- d- Ndf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)& ^* w1 c( c. `/ V+ t# d
df.sort_values('Grade').head() # 值排序
, g3 Z' R7 h0 T5 `$ P3 X0 BOut[28]:
$ l. S0 d* E. E2 N        Grade           Name  Gender  Height  Weight
5 x8 U0 K4 o8 |; ]! z0    Freshman   Gaopeng Yang  Female   158.9    46.0
0 Y; o7 }- V$ s& `3 Y2 _105  Freshman      Qiang Shi  Female   164.5    52.0& J% ?. m8 ~) n" V% f
96   Freshman  Changmei Feng  Female   163.8    56.0
* I+ P+ a5 [' g, |5 x5 \88   Freshman   Xiaopeng Han  Female   164.1    53.0
8 |% Y6 |  v" F. o; u4 H" _4 i7 A! t81   Freshman    Yanli Zhang  Female   165.1    52.0
  d4 g, u$ v; E3 ?- [0 E/ y2 h6 `& c
df.set_index('Grade').sort_index().head() # 索引排序, e1 L. b# p7 a5 I. A9 g) {& Y
Out[29]:
4 q3 q  s! O& K- \. d* F+ X                   Name  Gender  Height  Weight
$ m" o9 E) y$ i4 I! yGrade                                          # d( b! f6 @' ?) f( A; U! S4 ?
Freshman   Gaopeng Yang  Female   158.9    46.00 ]! {/ p- ?, [2 C+ K
Freshman      Qiang Shi  Female   164.5    52.0
) M6 b" S  `1 v& q9 k7 Y4 v7 CFreshman  Changmei Feng  Female   163.8    56.0+ E# c' Q1 U) G% [
Freshman   Xiaopeng Han  Female   164.1    53.0. b4 `8 v( a) a3 W
Freshman    Yanli Zhang  Female   165.1    52.0" Q: l* w& ^8 k: l! A
+ `* K+ C7 ^) `3 P  X+ F
1
7 H% I6 _" r  |" l1 \2' a+ v( H) W5 S8 U3 \- Q' n- x
3
, o' ]6 t& q7 _" T" Z2 R* Z" W# w4  g7 H3 R8 Y5 b/ J' ]5 B- h
5
  D+ ^5 T% J5 N/ V8 {6( I" N/ t! q3 i/ d/ ?
7/ {2 @: A) e, n% n2 N
83 o0 y: W+ z$ E* q
9
  Q" R1 u' t# J, n- ^% o0 W10+ _; h8 h2 Y# g7 j0 W' u3 [) Z3 B
118 a: H4 ^. W: L5 K1 a. A  D; B* K/ s
12; u# x4 _, u6 j- G: U8 p( x
13! d! ~2 t3 H! I$ O  W3 o
14* ~) i/ V* l* ?; f
157 n4 a, r# `, i. a  E5 ^, Q5 d) V
16
% @& y: _4 @/ F# _" b' I17. ^( u2 B7 P( V8 a
18: U9 l- W5 t3 ^& \0 o
19
! O7 c; h0 P- H* k7 k. n20- L; I# v$ U# B: W7 o/ j
  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:) Q0 S( ^3 G! `" I6 q  }- X
0 H$ O6 r) r. O4 `9 z# t
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
; Q5 B% Y0 P$ t% u>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。0 g! G: N/ a6 J' G) S
res1 = df.Grade == 'Sophomore'. v0 b5 x: ?$ t7 u
% G1 N; ^- e% W9 t' Y6 L' G0 y
res1.head()
, h; o" u) ^( jOut[31]:
2 X3 d# K8 Z) a% T4 R: E0 J0    False: r/ }. k1 U( G
1    False$ l( m5 r* F" o2 J6 y. [
2    False# Q& }8 F- ~- l8 @& T8 f) _6 T+ n; r
3     True" @6 m9 R% [* H1 J4 h
4     True% s4 D! Q. l; s
Name: Grade, dtype: bool
- i# [6 k, n7 R/ C
* K! o9 B$ O$ j) D: T- U, ^# V" zres2 = df.Grade == ['PhD']*df.shape[0]' [- f) ^# r0 F

' r& C1 x0 `; L' M+ w2 f+ V* Xres2.head(); _3 p0 p7 m8 M$ S6 z
Out[33]:
3 R. }; L$ L) H: V5 c8 i0    False
& K( @$ U( x, z0 ]2 G7 U8 l) p1    False9 P. h7 h* k6 d# w
2    False9 F% e& `7 S  e$ e$ I0 ~& M% W
3    False
7 Y3 Z6 i. P4 u7 K9 i0 \4    False; ?5 L2 s6 g. Q
Name: Grade, dtype: bool% a' L- M4 H& l; |; Q
0 \- V8 D( p& t8 d' \- y
res3 = df.Grade <= 'Sophomore'
5 p2 k: {- d9 l9 T  q+ m
9 q$ g- p% V3 D% Zres3.head(). D/ C# \! w$ I8 N
Out[35]: , y7 l  ~4 t; F8 ^2 V  B  ]" D
0     True
/ g' U8 B; K  t1     True. e+ ^* q+ I5 V: {, J$ g2 [  j
2    False* j3 x- `3 |% _. f8 T9 b
3     True
# H$ v# ^5 Z  d: `4     True
) ^: |) a( z3 m- p. LName: Grade, dtype: bool
& t9 [  G8 Y# _# e1 S5 z+ d+ R( o) P0 s* T3 g
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。+ P/ E6 E+ a( e& H6 e3 |
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
& B4 w: M6 x# D1 W( v) Z: b( R. H, B# k
res4.head()0 c9 t3 g7 W5 X4 b9 m
Out[37]:
" g$ u+ Z* B9 C0 W4 x+ l$ i" w0     True
' @' s! C; F! F. {( i1     True
3 E* Q- ~0 n7 a( d, c/ J2    False2 b+ y/ j/ X, N' D; A
3     True: B7 U, ^6 T( Z4 c) ]
4     True
4 E! h3 v- @0 m3 r1 eName: Grade, dtype: bool
3 H! ]# v# l0 M  z+ q+ t% g" ]
0 L9 P/ q2 j1 g9 B1( C3 J3 S! E! r" x
2; k& m9 m$ l1 ]
3
9 B$ m5 L) M9 M( @& q4: `: F$ i/ {2 x
5
& h( I9 E6 h% }3 ^# Q* R0 Z8 y/ g( h4 K6
0 \# _' }; q9 @8 J, Z7) d( `$ i5 m- B
8. n( d4 {1 d4 N9 B
9
+ o; s9 v5 z6 r10
, u, r9 v0 ^! g11
4 j5 _1 f: a! M1 S12/ a9 m# c: y4 l6 c5 ^5 x; n; H  V
132 d: ]+ r2 j4 ^1 h6 j, q6 W* K
14; [  K. U0 z; A$ c" H
15; i) E% ]5 k  s6 b! W- C) \) e
16
8 }+ B% J) M4 n5 X5 y- p) W17* k# g2 T6 c5 s1 n
18
8 k8 ~: y% h7 r, m3 f19
. X+ @. H! o$ e  o20# ^# S! _1 U1 @' x2 l5 h# P
21: d" u: }. G4 W* e
22
" [4 H0 ]+ c- ?# \; d23. G! B4 o- m) M* }/ C
24
' E0 W6 [1 A$ W5 z) E1 T. K252 g# z. X& N1 F, D( I# Z  d- K- s
26
- A2 C  K: N; {1 l6 p( G27
& n/ X3 u  |+ f+ b! x# @+ m& J28
4 I" y( x1 H0 D$ n/ t( P# X29
; r# N: }7 b' z, x1 \" t% ?30
# O, s* q2 r1 @4 K. [4 m. S+ z3 E31
* ~( s5 d6 r& A! D32: a+ Y$ h: d( _# c1 e) L0 s
330 P/ T6 T9 e0 ^; I, J+ G# Z- h
34
# V9 a2 B6 s  W; i7 a' b( C2 _35
/ ]  n1 A+ v/ _/ W36& W7 H6 ]) T( B+ p6 y4 N) j
37  o& E+ D% K' H* ^: ?7 Z9 u
38& I& g3 b1 s1 Z
39, b% a$ V7 S5 Q$ o7 i0 ~5 q
406 B) m5 J8 H3 C: G) Z
41
0 t" N" T# I8 ^, G: C0 |42
7 g7 k0 h+ k) o# h6 l! b434 R; ~/ q) j! ?/ l+ @( `4 @
44
: D( F( O+ P& J7 y% P9.3 区间类别
! [" K$ W# x! J' @% B9.3.1 利用cut和qcut进行区间构造
! W7 b; n+ F& _3 D  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。! E' T9 \# x, ?/ g
3 v& |3 y! b% v: N6 Y
cut函数常用参数有:4 P' v2 ^: ]  R& S6 A. G
bins:最重要的参数。
- o! E7 n7 k- j4 g. l如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
" {$ _. U" z/ w& m也可以传入列表,表示按指定区间分割点分割。: q) F& r0 F" X0 ?
  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。9 [4 l- c% V( A6 {/ C! ]8 B
  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
* l: s. P; I7 n% T: }5 U* |) W: j& {  S
s = pd.Series([1,2])
/ p* D6 @) u, X/ o9 l! t# bin传入整数; R7 K6 Y+ T4 k7 R

$ @. i# l- ?0 d7 w8 ipd.cut(s, bins=2)* j0 A2 f+ z$ A* S( T( ]8 B
Out[39]: % H# @% j, I/ E
0    (0.999, 1.5]
! P1 y1 {2 G, E- I1      (1.5, 2.0]
, C$ `. R6 c3 O1 c9 ?# y) Ddtype: category
" ?" {7 J3 A0 i6 A' ~Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]5 z- l5 @" W  ~9 |1 W

* }9 O4 e7 @7 Epd.cut(s, bins=2, right=False)$ f% ?$ ?5 `: K
Out[40]: 2 R3 @! g- K/ K3 ?0 N; R+ G7 T9 S
0      [1.0, 1.5)' j; d/ o9 r( r+ j+ Y
1    [1.5, 2.001)! p# e' T  B( z
dtype: category1 Y0 ]& p# U0 v! w9 j) B
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]: F/ J2 o* x3 c9 }( f7 G5 ~
9 Y* }: C4 G; V1 W" d5 |0 u
2 S' J. p' p: L, V
# bin传入分割点列表(使用`np.infty`可以表示无穷大):8 H& M' j. t; b* W2 q# |
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
! q8 \: _* a& K  o; T0 R9 YOut[41]: ; o: E5 a7 B+ }+ e4 k
0    (-inf, 1.2]
) c8 M. k) l; L) v  Z( h- h8 i1     (1.8, 2.2]4 R/ x) v- R! r: h3 I
dtype: category: }5 b& ~6 T% Z4 o& b% I1 R2 ]
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
6 _; C2 r3 t) W+ ]0 x: n# Z/ ~& J# r2 G2 Y& U9 `' t0 |, @
1
! l- Z2 Z6 J7 u+ p/ P2, j5 ?; X. \  A6 s5 R, p
3" m/ @3 Q0 R" l+ N; i+ [+ I, {
4
& Y, U+ `( `8 m  m+ H- r1 X5" x' i/ V0 j. k  ]# w& O* ~
6
* \6 g! k5 I+ X7
& K/ M/ \# ?* z: Y& i$ I% ~& I8/ r9 d7 `3 A) e" k
9: F; a# g- D2 i% @! g3 m
104 j, ]5 l. ^4 [
11* [% {6 i6 F. r  g; N
120 \  j9 v  ~! t1 |+ D8 q
13! b4 |' D! @1 t5 x1 m" f$ E
14
- r  i0 E- I9 q: Z" P15
# e0 h) x6 T9 c7 T: ?" H/ b16
6 C" a/ F: t+ k/ V9 L5 t17& P. F9 P: z( {. ^( m
18
" Y: W  ^  v, W+ I& |19% \( Z8 T1 `3 g0 S% K- k
20/ U$ D$ g, U' g; {, g4 _
21. N9 a7 G" m! b0 P! U8 o
220 Q& Q5 a' w! {* o5 e9 ~' W
237 x( R- W* m8 K+ @; h4 h
245 Q$ v. T4 D( e. D. x& D
25* ]& ?4 u+ e6 r2 d0 X
labels:区间的名字; u% X4 Q+ F1 m4 h$ ~# r9 J
retbins:是否返回分割点(默认不返回). I3 \5 D3 g& x% q8 \5 v9 W
默认retbins=Flase时,返回每个元素所属区间的列表1 t! y) l  X# y# W8 a* U
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
+ u! s/ l+ P: X& f! i8 y/ ]" w
# V# y: p6 V$ ds = df.Weight8 h  b4 N; T. q$ s
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
. i, J% w+ E' @# ?: Y1 [+ b; A. lres[0][:2]
; q3 @) a! n+ e. f) K* a/ S
. }7 O% C' M/ g0 A( \Out[44]: 4 u8 f. h2 u, i) G8 Q8 _! ]9 v
0    small
5 |1 o4 I: J0 r2 @" x# z1      big# \  b; j2 k1 Q
dtype: category6 a1 Y, L5 t6 c/ b4 v* s
Categories (2, object): ['small' < 'big']9 P2 T( s  s% a$ @
: R; S8 P; G3 [8 o* u
res[1] # 该元素为返回的分割点/ \0 {" C7 D# @9 p+ y
Out[45]: array([0.999, 1.5  , 2.   ])( o2 i, c4 W7 s* p# c9 C* B3 ]
1. }2 j' W" ]" X$ T4 V' R
2) K/ w: h- ]' U1 `
3# P' u& i, B" [2 c" r
4
  H% }6 R/ I$ h3 l1 X2 }57 m* o2 h+ X4 t6 s' i# b. j
6+ h7 K* G+ P; y% m, s8 N: X3 R* }
76 M8 @+ _2 C- T$ P  D
8
$ z; T% A" w+ D9: d- l' g; i. ~7 @4 b
10
/ u, |& e  R% a+ g8 X11( `" P$ f# ~* I( l2 N% H- i
12
7 e, v2 J% B* i. B3 a1 A" Gqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。2 D0 K; I2 V$ I/ |8 ^: [1 m5 Y
q为整数n时,指按照n等分位数把数据分箱; o1 V% ^- Q' d
q为浮点列表时,表示相应的分位数分割点。
- h; p5 m1 x, ?" T6 _s = df.Weight
( w1 t) ]/ {0 }; r% g
: E9 c$ O' f! t; Rpd.qcut(s, q=3).head()
& l" _: {) q& e/ W0 ~Out[47]: 9 B) L" ^7 x% G$ N+ O
0    (33.999, 48.0]
4 T. @. S6 M# ^+ k9 z, E1      (55.0, 89.0]
' i0 k) I. w. n% n( b2 n6 }2      (55.0, 89.0]
! F& O* U8 H3 Z* A3    (33.999, 48.0]
6 U$ E8 _" `( P! O' k# O4      (55.0, 89.0]$ G4 I: t5 @! t5 l& ?0 B
Name: Weight, dtype: category  S% i' |5 j* g. u7 T' O8 `5 A- f
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]], A) Q; v' A, p

: a7 f% c+ c; h0 gpd.qcut(s, q=[0,0.2,0.8,1]).head()! P2 |3 G, G) e2 a; v/ g% b2 e& N
Out[48]: 4 W1 n  {5 L, f/ E# a. c
0      (44.0, 69.4]
" H* s6 ^/ \$ M! O2 L# n6 P/ @1      (69.4, 89.0]3 a: Q. l$ c, k$ h/ [: N
2      (69.4, 89.0]5 Z# o1 {. x3 h) L( P- F
3    (33.999, 44.0]  T8 ^/ u5 _  @3 w% T* I3 r
4      (69.4, 89.0]
0 B2 e/ X. O1 BName: Weight, dtype: category
% \! r: e2 ?3 M# gCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
6 A2 n0 E3 i! q* M( }0 p, j3 D( |
1- U7 t& h- J+ j, w9 z7 E. h
2
& i) m  }' J6 {  K% }0 ?3
4 D/ \+ S. W/ z# j* y$ Q4( n2 N, w4 x7 B+ {2 J
5! Y2 H# Y; a# r% O" A8 T$ O3 a* b
6
& l4 o+ q6 a3 j7
, f+ m0 m5 A# n) z85 {6 k& U. u- j# a  f0 ]* Q) \$ b% x
92 D! ~: ?2 f/ G. w6 V. r/ f
10+ E- C1 v/ l! K' E' w
11; U# O5 S% Y5 f
12
" y* m) |- h2 h% y9 a- \13
* B$ W& [" l  [9 Y5 X14
: v/ b0 W9 g+ z" }3 _9 u15; d& n" s. \; O: \0 L& A
16( W: ~+ N% x* l  ~3 K
17
1 }) h; n6 t2 H% O" ?187 {9 l: r2 T& t! Z$ D, S
19
# t7 y, L; H0 @9 M3 R201 \4 e5 i. a% S
21
7 d" y5 N6 p# [- o! ^9.3.2 一般区间的构造* r; n, S  _# ^" S
  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。8 H2 h, h0 E1 f* o8 Z5 a

! F% X9 v- i+ G, i6 n9 z  h开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。1 S' B# f- @6 F7 p0 d7 v4 O
my_interval = pd.Interval(0, 1, 'right')
8 q. o) R" p* W0 P$ {9 c3 P4 t# p  F* E+ o. Y; a
my_interval/ y! v0 K; y  e; R
Out[50]: Interval(0, 1, closed='right')+ U$ U" O$ G3 I2 m* k
1' {% N" l' D" Y; K8 p# A
2
) l; t  O( ]6 @" F2 j3+ x  F. R( G& g
4
$ m3 C: c  P% `4 h6 N7 \区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
$ m& J) B* M* Q" j( G使用in可以判断元素是否属于区间+ F: }7 a7 X9 k$ E3 H7 y$ S! o
用overlaps可以判断两个区间是否有交集:
6 c" B- ^/ C$ V" |- j0.5 in my_interval8 m1 ?5 ~( n5 ?( L) T
3 d6 \0 `0 R+ d7 c7 {/ [9 q9 o
True1 f5 Z( p% A- t4 H
1
" b, V# H4 c  Z% s3 w7 U  f25 k2 G9 v; r% A' _: h$ d, n' a' P0 z
3
: L) W3 ?! I0 n2 `/ I; I( umy_interval_2 = pd.Interval(0.5, 1.5, 'left')/ J# Q, K! S( F' c6 _  |/ A
my_interval.overlaps(my_interval_2): ^1 g/ e, {4 C+ {0 g
, Z- \& v9 Z2 o, G( a2 Y# w& b6 z
True
7 i4 N( a" y1 q( I) S+ @& u! x1+ [; a1 t! s9 R; S1 P
2
! E9 F1 J" h! g/ K; F# S' ^7 h3
, s5 y: Y* A3 i, Z; Y4, W" A2 x1 b' F0 ~- F- X2 I9 K: V
  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:" s: u- {! t5 ]& n: Y! q7 `" @5 ]$ Z2 z
! U0 P- T) M" p; B# y
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:/ c0 b0 `8 O9 \; n. O- S# `' m
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
7 j+ y: F: D6 W3 t; H% k3 S; Z, {; {( T5 b$ v1 T# v9 B
IntervalIndex([[1, 3], [3, 6], [6, 10]],
5 I7 A9 b+ G$ [4 K; r1 f# ^& I6 x# ^               closed='both',
2 }' }* t0 @' T& r: [               dtype='interval[int64]')/ w$ s: [4 }. o  d- G- e/ K8 w
1; b% z% A9 R3 ?' G, a7 `' t
27 k9 F- z8 [1 A" ]( D  ^
3
8 a* g1 m# E+ S: V: r% n4
' U' I5 U; N. Z+ Z1 _5
2 l" ]- @- Q( jfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
7 @) X+ A5 |2 ~; Q) O7 Xpd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
8 C4 p/ }$ T$ h+ e$ v8 E
( f( }! @. l0 R7 @IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
3 h: {, a# z3 G                  closed='neither',
5 \0 t# @4 K* ~; V: p7 E                  dtype='interval[int64]')
, f4 `' [6 S5 C& H- }. U+ q4 \1/ _2 _2 {$ y1 V, r/ e* C
2
  }# N3 ]/ ]0 k$ Z% b3
0 e$ y5 U6 F4 e# N$ F2 u4
6 \- Q8 C  a% g4 J( \' ~2 i& W56 g# [4 |8 \& R+ q& q  n
from_tuples:传入起点和终点元组构成的列表:
5 v0 r8 @* j! ~. ^9 e7 u: npd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
, I% ^! n& t& n* h9 A/ M) q+ m/ G3 P
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],# `0 p( \. b- l0 K% Q
              closed='neither',3 ^. W0 y# d, a% F5 U4 L
              dtype='interval[int64]')
! G. u3 Z/ V7 C, ]# _1
2 Q" E, F- o1 q/ X( C& Q# V; d2; l& I2 _/ M$ h' Y6 @- c3 m
3
0 x& O+ I6 F, G( F# l8 Z4 x9 s43 G% b$ J# m9 B$ V
51 \8 ~& @  d/ x6 |. [
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:" F4 O' f: R& r# @
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
7 {9 Q( S- i% L9 ^9 [, E7 N8 ROut[57]: + E  G9 n' u! \9 k- }1 I. x
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
' u: Z9 z! D! L- n- U/ X              closed='right',1 T( `9 @9 ~  |/ l) p# ]0 Z" ]
              dtype='interval[float64]')' {0 }; ]! ]3 R+ c6 j7 E
/ L* v: _/ M( D3 ]3 G7 U8 E* p3 W
pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
1 s7 a/ F( m! L+ p2 mOut[58]: 9 N! O& m- s3 y5 n8 o: n8 O4 s
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],5 X( y2 b& o% s4 l2 V0 {' |
              closed='right',- |! p. w2 p( m6 B( H
              dtype='interval[float64]')& s7 r5 I3 X" ^- U
1
  U4 v! g7 _& l5 D2
5 F% X" M) c, j7 w5 ~3
% S: Y! d7 Y9 o2 D1 Y: w4" a" z1 J6 g/ Z% a2 s% {
5/ D+ ?/ F! i) g# i9 H& |
64 g# d9 f+ F& |( [6 R2 c7 P/ |  F
7
( J9 z: l' k; f. Y1 O8 ^- I! ]8
& O  l  n  j% g3 R% v, z* E9
& @) L+ Q# X+ I/ V  I10
# e5 x4 J5 X+ B11+ W8 B1 _8 y! H3 T; x7 i* }, |
【练一练】
: @% w+ Q) w" G& Z  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
+ N3 b* W2 r4 N
% ?8 R# G. B: Z/ ^  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。& Z+ h) E' J, i& o
2 p+ W" |( Q, Z  A$ r
my_interval9 Q2 m- L8 x& o) t6 u$ |4 q& y- g
Out[59]: Interval(0, 1, closed='right')
: U" ^, L. c4 s4 N
* m/ p, b! @" l+ H0 ~5 Qmy_interval_21 ^' w" f( O$ b2 w+ Y/ q& \
Out[60]: Interval(0.5, 1.5, closed='left')
, O' y7 |# w  B5 _" j7 a5 W7 P" X
# X3 U; x9 u  s' rpd.IntervalIndex([my_interval, my_interval_2], closed='left')
& _- |/ \% E: D' @% ~2 KOut[61]:
' \& Q' H: L, N( D; c5 UIntervalIndex([[0.0, 1.0), [0.5, 1.5)],7 V4 m) v: g. G3 N
              closed='left',( m* {/ K9 U3 |" B9 j) B4 p% d
              dtype='interval[float64]')
/ N9 v+ m! C4 k( ?1
+ B# H5 X7 u" i: Z24 h9 A$ S' `" D6 }, P7 Q
3
7 C) g9 I7 s3 c% ^$ l0 P# y4
, A; E: D# b) ]. w) x4 O9 O5
  E& i$ F/ M! F: P- e. k3 b' S6; u' w- e& x$ }0 P% R
7
# d+ K& l  e0 w, y( M8
& T, T1 Q; c& p9
5 p( P7 p7 z; R' `! [10* j" o2 q# \8 `3 u6 @% |# h1 Z
11
& @. j( y6 @+ {# a/ @9.3.3 区间的属性与方法
7 Y6 Q9 L1 G+ j; R- ~* K4 v5 B  y  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
" s. P6 ?0 k- d  d' O  a- I0 k
! K. h: [6 [7 _2 R, i. C+ K# Ss=df.Weight3 P7 _5 ^( j' F+ D
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
, J9 Q: r' b0 P& R4 C( xid_interval[:3]5 N) t/ C: F# _9 J. ~$ }" j4 _
5 B/ Y; f1 @) h% s* ?& A
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],7 v* d& V# E& V: W8 j! Q
                 closed='right',0 l! a% g8 K+ B
                 name='Weight',
9 \$ v; p) P: ~- h                 dtype='interval[float64]'), x+ @; J7 \6 F+ L" H/ D
1- L- r) `! M1 a/ s2 M6 ^9 o
2- j3 b  h# e2 o3 ^, ~- d+ E
3* T; [0 I! b/ Y4 `3 i( r
46 w$ D9 m6 Y, L( X, D( W6 P
5" F0 G; M5 r2 g5 N( Q3 H) i; r! K! ^
6+ q) ^9 r$ {, k" H
7
: B+ E& c% m8 J2 J( c9 C. P8
6 m+ f4 g; a4 a4 H6 J; w( p2 N# b+ E与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。# q7 P4 k; P/ s6 n0 ^" u, v* r
id_demo = id_interval[:5] # 选出前5个展示
' \6 j0 }' [" M$ v  Z( Q: |
8 a1 O9 a% ?2 q) j! O8 qid_demo* @! a* o- q& ?; X  h" _
Out[64]:
, N  N& X' r% G0 I# KIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],; H  l9 Q- ^/ w% y1 s2 I2 N
              closed='right',
( E- p. X: }: N$ Z) `              name='Weight',
; g& n: L1 s' {7 A' n/ t              dtype='interval[float64]')1 a# \  f4 O$ [9 A5 D1 t9 v

! H/ i1 q* W4 K: Z; q3 Jid_demo.left # 获取这五个区间的左端点+ V- z) w& k! V  N: F- t$ v+ T0 M
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
- Z/ S7 V' ^8 z/ v
! M$ K6 g* H; |/ {* c8 o# @id_demo.right # 获取这五个区间的右端点
7 q$ V4 P  a) N+ E4 p, ROut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')( T8 k  v: g( c+ m6 f

+ B; X; \4 j! I0 ]' Did_demo.mid7 S  ^3 L- z9 R6 Z  B. s3 F! n: g
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')+ _/ a# L: i) V; Y

" T9 h8 p5 K1 o$ q) z  `, Pid_demo.length: N( J8 j  a. k
Out[68]: 8 k- h5 `: T. a/ G5 n9 p
Float64Index([18.387999999999998, 18.334000000000003, 18.333,, u2 ?5 Z% |: _+ S: T2 D! c4 k/ }
              18.387999999999998, 18.333],1 q1 i- n6 {' A3 V* l8 n. K
             dtype='float64')# \# q1 `" H8 m, S9 u$ g. L6 K* t

8 \+ I; u8 |2 P6 n% ]0 B% `: t1
" p6 Q) L4 T  O+ j% V2
0 L7 A# d3 W: n# m3$ w. ~; R8 H1 }7 t, c. \
46 d' K/ z* V  l/ o0 K. N* p! ~
5" N2 v& E$ P) X; l% q6 D* J
6
/ E% m6 `+ E& _7 ?0 F7
" }8 R: _" G0 H; l/ F/ U8% H; X! n% c0 ?8 Y( I  v' f2 e, a
9
8 k  }' U% ]7 I% I7 x% b10
+ N4 K5 ]8 r5 e+ T# K3 u11
* V% h4 R2 M" d' b, U12  m. k9 E1 L7 x) ]7 G( a7 b6 v
131 N' a5 ~% Y* F! z
14
# F% O7 h: p4 G+ _9 k- v2 T! |15
# Q# ?* R8 b! ^* Y16
/ @$ G9 _7 x) h& j, K17: d: h. ^- a+ a/ Y3 b
18
% C7 c" Y+ M, x! [- M, N3 F! k' X192 a* ~) p3 R8 O& T# I. ?
20, O0 d  w: J* O- u/ C  |
21
( Z: B: j( M! l- o- N9 U/ ?22
* q6 B0 i7 E& |! w; F: o23- ~0 ^% `9 k1 S" N+ [& ]+ L3 f- d  V
IntervalIndex还有两个常用方法:( Q+ n. R+ x4 D7 T1 `( W9 u
contains:逐个判断每个区间是否包含某元素9 |6 E6 }" e4 m  v# K* u) K
overlaps:是否和一个pd.Interval对象有交集。
* F; ^5 _# n# d& Qid_demo.contains(50)
4 ^8 U9 x' o3 O; [0 wOut[69]: array([ True, False, False,  True, False])7 _5 y; l/ z0 h, I6 J9 N
: J  I4 E2 \/ S5 J5 }1 B2 w# U, |
id_demo.overlaps(pd.Interval(40,60))5 e- x+ h: Q2 \2 h/ R
Out[70]: array([ True,  True, False,  True, False])! e- G6 [# N- {3 c1 z
1
% m3 ]& R' ~8 Q. C28 p6 K' ]/ u7 C
3
6 a* v3 O/ W# c2 X% U43 U: |, j% x. B1 i
5
# Z& f. {1 f4 B2 T9.4 练习
% {$ U# m) D6 z- L) t3 G) ZEx1: 统计未出现的类别' y, O8 m" [$ ?. r- ?/ C
  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:3 F, X0 {, m! Z

$ ?: b  ?2 \: v$ `- _df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})3 v, _' c% K3 L
pd.crosstab(df.A, df.B)
' T  C* i9 ?" o5 j) l3 u1 ]: y1 G9 @; c- A: W9 Y+ _4 C$ ]
Out[72]: , ]1 _0 N/ |, Q' R4 _
B  cat  dog
( t2 e3 |! }0 {A         
) T2 Z2 R( M  _. P: b7 O* N3 o/ Xa    2    0. d: S- X/ V" ?+ M- t& H' x
b    1    07 J; M5 ~+ z; ?6 I3 H. C( Z* y7 ]
c    0    1
. j* }& O* d# ]& x% M) r9 L1% Z  x) K$ W  T0 Q
2$ S% B& q# ]" x; v$ p% M
30 U/ @$ _# Z, q9 c/ x  G, ?
40 K# N3 P5 n8 }5 _7 X6 N# b6 F) {
5
4 S7 f: Z7 q1 d* z8 r0 I6
! S" r1 k- H9 H9 Z9 l* G# S7
/ f9 P8 z4 K+ M8
; W2 _. O+ L( c( Y1 ^; d/ [9
2 s* r. u( _  y3 {  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:5 F& J0 B9 h( [6 Z
/ W& j# \6 {8 a
df.B = df.B.astype('category').cat.add_categories('sheep')' {0 [" [; {) A
pd.crosstab(df.A, df.B, dropna=False)
* R/ C1 ^# d$ u/ h( N8 `3 n' u
Out[74]:
2 {! \/ q6 x( C2 s% fB  cat  dog  sheep
! M$ G+ }3 ~- A$ O' fA                 ' Q. ]: Q1 X" o$ H' K
a    2    0      0
7 D0 m/ B* \7 Gb    1    0      0
! y" l) Z7 v( f' Zc    0    1      0+ A& s8 m' u$ v! P* M( L/ j
1; J2 K: E6 c9 R- a5 ~2 Z+ w
2
2 k, Y, V0 H/ b) o: x+ S3
3 t' @# I: n  g  @. _0 o- S8 u1 {4
% O: m, v. ~. p54 f6 Y) D: i0 ]" K; _
66 p9 `& _' x8 h* f* j$ }1 K
7
" A5 }7 M) q' L* V8
2 I0 E. S$ x; r$ M. `; L" c9! S) L$ Q4 K/ Y2 U
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。$ @( M7 l2 Z4 `, r4 y2 @
! Q+ ?2 I/ F0 a9 q
Ex2: 钻石数据集
* ^+ Z+ x: h3 b  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
7 z( `1 d+ G/ ?' h3 e8 Z4 d, Q3 w0 g# I( @0 ^; @" x
df = pd.read_csv('../data/diamonds.csv')
! [; p8 v  p- V2 Y- e7 e- Ydf.head(3)" U" i7 m4 B2 I3 H: e# E$ U
+ C7 ?; Y1 a0 b6 r
Out[76]:
: P& y3 \  B: m# H# z  ~4 `/ C   carat      cut    clarity  price
9 |. k. P6 M( [0   0.23     Ideal     SI2     326. @  b1 w. y# b% d3 D& m4 k- t
1   0.21    Premium    SI1     326
7 ~0 t  @5 V, Y5 o2   0.23     Good      VS1     327
: K+ N' n8 z3 Y) |6 h. q1
; n6 F. q; J% q% n1 \1 E8 P28 o+ `+ ^: z8 }+ J3 z
3
! C* n. H* ?, P( F. ]40 b. x& h, d2 O. K& V2 ]
5
3 _8 |% f7 \. O. t- P6# P0 y' s8 {3 K
7
1 g! r$ }, T4 q$ o6 n7 s5 w  H7 G8
- d% Z! E4 t! ~" X  E  c/ H! o分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
2 w: I! I' B# }! D) f9 `5 m钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
9 b/ S% E8 O! K. g  w4 q) J" L分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。' ~$ r8 R+ K. ^
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
- [7 }( ~( K5 O第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。/ V! j, l3 i' U6 ^1 v& G2 B
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
  r, y( H5 E6 R. _# p1 f先看看数据结构:
7 P$ x5 _% G. D( x
- n& A0 n" z6 M( r; adf.info()
7 _& {5 y& J& v& u$ c7 rData columns (total 4 columns):
! r- i, x" W: f: L: y6 ] #   Column   Non-Null Count  Dtype  & X6 J+ w0 ~, S: t% S! ^# k
---  ------   --------------  -----  
- g- ]- t4 f1 `) d" C- s 0   carat    53940 non-null  float64
9 g: i$ v  q% j1 d6 S( z 1   cut      53940 non-null  object
6 L/ \3 F! z( z8 P8 d: M 2   clarity  53940 non-null  object
9 Q9 G  B1 M$ w: Q" P 3   price    53940 non-null  int64  * ?- l: C! D* h+ m
dtypes: float64(1), int64(1), object(2)4 v1 _+ a0 p: q5 Z* `$ `) A
1
( y8 a% h" K8 l+ C2' Q3 }& s8 j! l
3+ R* U, `0 P: _3 a8 p* \7 I8 `
4- N5 {5 U" c; m3 w! s
5
  R! L5 ~4 ^/ b( F% g& r$ ^" E61 C4 U3 ^0 K) |
78 J7 l) Z4 A; u8 W8 P
8& P1 D5 W5 A( _4 B: s- G# e
9: d" B6 m- l, Q0 l: T7 C$ O
比较两种操作的性能: ~3 X1 z  u* x# \7 B
%time df.cut.unique()2 T# D, B1 T1 q. k8 Z6 N

; Q# @, d0 T7 qWall time: 5.98 ms
6 r7 z8 \0 E1 b6 \+ t7 y+ w4 u" jarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)! o% J$ f1 `0 v4 X% B
1
  P4 u' c4 r+ `: l* b- Q1 F$ W2
2 Y+ G5 z( n  O) h0 g& ?, `0 E3' h8 U* S* L( a7 f
4
1 `7 `. i6 p" t; ]5 @2 K2 [+ X) }, A%time df.cut.astype('category').unique()
, z2 c3 o, V9 I1 S6 `) N4 [8 j* W& B: y# o1 n, s' J# ~, i
Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms
9 z) G" M2 U5 U1 v3 B/ t8 f* a" E% n% `['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
5 V8 M$ O4 f% Q2 Y# G5 zCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
+ J6 E4 k. ~  n5 ~( ~1
, N' V6 L. T4 W2
/ `* ]4 |. J- K! W' G2 O8 M3
6 }# p1 a% w) j1 ~4
: V6 a$ }1 w. a" ~5; L! f2 V$ q8 D; o5 ?4 ^
df.cut=df.cut.astype('category')! _" G0 g& K- v
%time df.cut.unique() # 类别属性统计,2ms
) w2 y7 A3 V  I& P6 o" G1 P, T6 w* K7 O( V5 t
Wall time: 2 ms
; p9 C7 n* g, F) w['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
' q) v! ?. V# m  |; t  `Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']* F7 T9 z+ s% N6 z/ X) u
1
, U/ ~6 ^4 ~/ [% [' W# M( y28 g& c- q, d3 B' x4 S
3
. p3 w& |- S3 ?1 @# l: O/ q- r* i' _9 P4
! j" j4 @' O; l3 N, r' @5
6 v# z/ A  {8 U! x3 j; o61 [; g( s5 a+ x, q( ?" E
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
. ~* l: F2 R( E) J+ Xls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
- K4 Z" }/ n- P' [ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'], s  F& w2 `4 _: M# K( t
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
+ w7 l  H- V. T7 t$ x' R* Cdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)7 u3 A* D$ S( K8 ~! r  e
5 n( E7 u4 w7 T5 Z6 ~
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
) W" R4 a" u: t3 X
* U$ |% f1 W+ [, P5 D6 n$ u- C        carat         cut        clarity        price
, l1 k& m$ E+ u& L. Q315        0.96        Ideal          I1        2801
, Q3 s3 v( a$ Z- C  A4 k( _535        0.96        Ideal          I1        2826" L. g9 ]8 C, [7 e
551        0.97        Ideal          I1        2830
. Y+ x( ]6 W( c- o& ?9 a1- L. M8 G+ t; H* P& q
20 u1 v5 G; S" C
3. t3 U, B+ E  [
4
* B* L( H! p$ m1 s; Q5
' T- v8 U8 Z, @+ T4 I6
( L  ^6 J( M- k1 z6 b' P8 g: J7$ n2 ^* k" Q% u9 N: m0 R
8
, v3 L" j" e5 H" n8 }9* h  ^: M# B7 [0 m% Y! G6 h6 ?3 {. K
108 X) p* H4 Z7 m$ x
11, O4 a& v; D5 O4 N6 j
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。$ B5 B" j+ O: R  I2 K/ E
# 第一种是将类别重命名为整数
& m4 R8 s+ j; ~$ Wdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
) f5 v' ^. O! S* Ndict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
- K5 P7 m5 K5 }' z: d( x' g
& S' g7 Z: {6 s8 A$ A) udf.cut=df.cut.cat.rename_categories(dict1)
) M6 l5 p+ ^+ ]* d% O" x; ?df.clarity=df.clarity.cat.rename_categories(dict2)
; A5 {, |8 P" ^1 \df.head(3)
- D0 `/ k. s! R3 g2 ^. b* T- w! {% O) ]( r
        carat        cut        clarity        price
: O( w3 M5 k; j$ m  A8 {$ `8 ]: T+ x0        0.23        0          6                326
3 N. L# j( a6 s1        0.21        1          5                326
. L) \0 b' g7 ~3 a9 l! R/ H3 o2        0.23        3          3                327
7 y2 x" F3 t1 {) c1$ {+ a! j0 b7 L
2  r" D! v2 c9 N: s7 J# T! b' C, c
3" m) K& Q; z7 Y9 |0 K5 E3 t
4
/ H" M; a( c. Y) F  G% d5
0 f& _* m& k! d1 p2 S2 H  X6* L- ?4 B0 R# j1 |4 z+ C! U# d: r
7
1 F4 I2 p  y2 y4 c' S8) ^1 x( F) ~& ^) u9 ~* e9 m
9
) a. G5 g' g; n: X4 m4 t1 i6 |106 I5 x- M" I/ `2 s- {9 _( e( k& ?
11
, U# R3 F: u% k3 K, z% n12
7 I* k6 I* p/ I* C5 D* y+ j# 第二种应该是报错object属性,然后直接进行替换
( Z* B+ U( d: hdf = pd.read_csv('data/diamonds.csv')5 N7 H7 A# ~* u0 G! e5 ]0 j! t: |
for i,j in enumerate(ls_cut[::-1]):
6 d1 o3 M  {# o! H" \! R4 X( L    df.loc[df.cut==j,'cut']=i
9 ~6 R' W4 J) {2 P- U# y; ]1 F& a  V& H% W
for k,l in enumerate(ls_clarity[::-1]):
* d& @; _% j0 H! \1 X, C1 _    df.loc[df.clarity==l,'clarity']=k  q) d) ]2 ?( ^! V; G% \
df.head(3)9 a* k- z3 k" L& O) C+ h/ |

' q2 b! s6 }2 p) B7 D7 d        carat        cut        clarity        price
- t! V7 y' Q7 c. ^) r0        0.23        0          6                326- r/ ^9 n1 U( Z' T* W8 P/ P  h
1        0.21        1          5                3264 K& u6 w3 B, U7 s% ~
2        0.23        3          3                327
" i1 I: R) O+ T18 b! s* x- y; C5 y
2
5 N' m3 @, T) o6 u# y/ t5 M31 b1 L; T  o' o2 ~, \
4- f# T/ P/ I+ Z: _' _6 H# Z
5$ C+ J0 W. m" v: u/ b$ b/ K
6
- A4 b; ]1 {5 w$ I% D0 _/ `: R7/ a* o8 q7 i6 a, P& k+ K% e
8
) `( m- Y; l- b/ K3 B" ?  }9
3 S6 z5 @& d) j* i/ g* e102 j9 W' w6 M9 ~. _; g( a' z
11
6 C2 X  w4 m- {, I: ^& ?  K; l120 r7 I# p& W# h. y
13
" m" |; d3 h) E对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
% k' Z! I4 @7 G2 G; }" C3 ?# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点3 [: H* A% Z1 U! L/ m) s
avg=df.price/df.carat5 ~6 U6 b1 z, [: D3 [! t/ {  S' i
* Z' R$ W- b, ?* @8 o9 m% X' e7 ^
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],8 c; u0 Q$ D  J. a1 v
                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]( ?  i6 \. e0 O9 N
& ^' ?3 _, f% u- F; K
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],& m4 [8 C6 c  _+ b0 q8 V2 ]
                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
4 D. {/ p% S$ u8 odf.head()& G% M: ]: j  J& x
  n5 w& f7 |" O/ c; a: ^
        carat        cut         clarity        price        price_quantile        price_list6 B' A" c) o5 m  g- X, i
0        0.23        0                6                326                        Very Low                Low
( J& F2 |1 L1 g4 i1        0.21        1                5                326                        Very Low                Low
8 U1 d1 v5 U: z7 ?% @2        0.23        3                3                327                        Very Low                Low( l" S  J, A: c7 c% [: d
3        0.29        1                4                334                        Very Low                Low" W9 w' Z1 I  D% r* h/ s7 u6 ?7 g
4        0.31        3                6                335                        Very Low                Low                                       / a+ z6 I9 x& M
* x; M' v  d5 F1 x/ t9 p
1# t! w! I' |5 P; q0 q, L# I# G
2
- ~2 c3 i7 r5 q7 L  e3
2 }' u$ V7 x+ R4 J+ [  ^) R9 W4
) Y4 e0 \! \8 e  L5
9 H( V5 f$ T) e$ g6# o! w2 H3 r6 D5 D/ i3 }
7' ]1 ^! d5 `& ~- T" [! ^6 L
8: x7 u  B8 R. h5 G! ~5 H
9
# G3 C6 d& b: W10
% e5 A. q$ G, i+ C8 w- W11' w, Q" E6 n6 D' b+ S. |  i
12
: C  t, e4 M% Q2 P  C2 Y13
8 U  h/ n: y7 H& x0 B14
8 k& C: }1 D; z: j) t/ r15
8 V8 x- K6 o: T* ~16, c/ ]4 q, K) ^0 ?' @5 m" o
分割点分别是:
# y; e7 j' w* p; B; r
. I; U2 [+ ^% s# D2 t' Narray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])( b9 _! }' O  ?- j3 T/ b
array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])' _" A6 z" s! q0 w0 q8 E$ i
1
5 |" P+ ~. d5 |! C; M* I0 o2
/ Z. t' ^# l6 }$ G第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
4 P: |! Q' P) Zdf['price_list'].cat.categories # 原先设定的类别数; p" L* |1 e. _1 l, d0 w
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')( v- K4 D. b+ {, f
' ?) j  }! e# ^3 v
df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别8 {5 g/ Z& `3 L, L0 ^3 X0 }( w; N
Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现" k" e% B0 f- U" S
11 a6 R  E4 @' x6 o! A/ S
2- C2 t; Y. W  y. y
34 V2 S" Y- p# D0 Z- o
4
* ?- x9 }, s/ r  g; [) @5$ k5 H; {, o8 G& |' x3 _3 Z; ?
avg.sort_values() # 可见首尾区间确实是没有的
/ S* L% S9 G( R/ ]; e31962     1051.162791' V) T' g1 a, R5 l) H
15        1078.125000
8 x7 A0 b/ M. e1 u3 [4         1080.6451617 H0 s' P# B# d
28285     1109.090909- v- t7 }& ^; X
13        1109.677419
* R5 }6 M# c9 H- F  _; S             ...     8 s$ T. C; F0 o" s$ m+ Y
26998    16764.705882# @4 H' r2 B7 \/ M! H
27457    16928.971963' o3 M6 `8 t3 \7 s6 W' r
27226    17077.669903
: U+ `7 W5 _! G27530    17083.177570# k  D1 Z9 c# p' v5 w3 J
27635    17828.846154  Z1 U! {3 V# e
1
* z+ r# A2 [, A+ u( C7 x2- p' |+ h! ~6 \
3
; M% c1 t. i0 m7 h2 y; B) j9 \4
1 I: H0 L; }, A( }3 M5% m/ P- m  I9 H0 F$ ^, x3 w
69 {& Q: n+ ]& c' L4 g( d& Y; e# b
7+ Y  `2 m7 K2 T( Z3 |, t9 S
8
& j5 p! {$ u) l: ?( Y9
; Y- C. c1 a5 A3 @) g. [2 A10/ b  A; ^2 ?; {9 a( {( n  h
11# `1 k0 O# r1 ?- B# \8 m/ o5 ?# C2 F
127 F: ]/ w, \  F: |. N
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
% z9 F' {/ y+ M1 x6 ~# 分割时区间不能有命名,否则字符串传入错误。3 |% K$ D( J2 A
id_interval=pd.IntervalIndex(
0 i; L9 W9 p. T( k5 C    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
) N* f8 s9 @! k* S                            )' z% |# `4 N5 U+ @1 D! f! c
id_interval.left; Q- f5 x' q9 R. v' ]
id_interval.right
4 S3 e+ @* j. {1 ?9 {" U$ Bid_interval.length                           
, }9 h  M; A$ n0 j" f15 l0 l; [$ j1 y
2
3 B+ j: ?2 i8 l5 v* {4 h$ t6 z3: C/ e5 O* I: D: ]
4
" P  l! Y0 a4 E, A# p8 W/ U5 ~% z8 Q5
$ J+ D7 k9 W! h; `7 w6 A6
, y4 ?) x3 M' v1 L2 k7
. F. ?2 O) z% I6 J8 |第十章 时序数据
. f) N$ {# h3 timport numpy as np$ d2 O6 |7 O2 h0 ~7 q  {$ Z
import pandas as pd
# G$ Y. p- r& w  |7 H( z15 J& ~: F, u% [+ t' b" L
2
" [2 K" \2 W! m5 g" e" k  \1 L$ W$ N  G
0 }3 z6 o, a0 B( H0 T2 E: N6 c
10.1 时序中的基本对象" o- _* ^: R  h: `; [/ n0 ?
  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
$ A+ l, J# I! `& i9 }3 ~# D& u* g+ y( P6 d2 I: p9 V
会出现时间戳(Date times)的概念,即’2020-9-7 08:00:00’和’2020-9-7 10:00:00’这两个时间点分别代表了上课和下课的时刻,在pandas中称为Timestamp。同时,一系列的时间戳可以组成DatetimeIndex,而将它放到Series中后,Series的类型就变为了datetime64[ns],如果有涉及时区则为datetime64[ns, tz],其中tz是timezone的简写。
$ b4 I; I3 f4 z, c  V+ z# T7 l* b3 E/ K0 J$ Y
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。$ V+ [% f# M! z9 w% c7 ^) R
% |7 K* Z! i- z+ F* m- s) X- C
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。1 @% b# i* x: U
) h" H: S- E$ R
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
6 e: `& t( N) h# F9 o% W2 V
' \# ~( {8 i2 @* X  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:' g# H- p, t: a  X3 {. E! R) F

5 L' G) y& T' ~概念        单元素类型        数组类型        pandas数据类型
9 [# E. q" H2 V+ t0 ^Date times        Timestamp        DatetimeIndex        datetime64[ns]
0 k4 W5 r, E  O" s* d; |Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]: ?% W( \4 B* @0 j4 h0 y1 X- z
Time spans        Period        PeriodIndex        period[freq]# I1 f* n' D* m) D1 I9 ]+ j0 x
Date offsets        DateOffset        None        None  W' H5 F( U+ P4 x' M! i; p
  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。  j9 y, H* e1 L. i0 p

; g! h  }3 H- i# w5 d4 X2 h10.2 时间戳8 `6 Q6 k3 \5 J; x0 K: K7 ]
10.2.1 Timestamp的构造与属性
" A- l) f  t. `  m单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:8 K* [/ L0 [7 ?' g
& j, R) Z7 u% Y: ?# T! @
ts = pd.Timestamp('2020/1/1'), A0 f7 V) C" _$ \  i* |6 R! h, N

9 p' d: w1 @& w/ W/ }* _ts% e) V+ K0 h- G, l
Out[4]: Timestamp('2020-01-01 00:00:00')
5 C4 Q" I, B. X) k. \, Q+ t' q" O- K8 o7 B" {8 q, H
ts = pd.Timestamp('2020-1-1 08:10:30')
9 ~- F& g( L4 y& t: k( w2 O9 X
1 q  u/ _8 I# a" Nts
: n& e4 q. L  z# U6 w9 G' n  ~Out[6]: Timestamp('2020-01-01 08:10:30')0 o1 {) O+ `6 U* `% l
1
$ ?! f* [9 b2 b0 ]% q2
3 W- \" F4 ~# {1 b# e0 e+ J3
: L" k, y; o- ^: E) d2 g4* x9 u6 j  Q, g# O1 Q, N
5
8 H- x/ o* e: l, d) B5 F6
' H8 K& }" x' y6 i7
1 o/ }  v) ?( D* z6 q8
4 }! r6 ]7 C* c- Y8 @( _9
* a2 ~" T# S( c- a6 i- U4 I+ B通过year, month, day, hour, min, second可以获取具体的数值:# v: l$ M& s1 U

  ~# L) \- X: t7 @" fts.year
7 s+ S  h% r' }+ S5 xOut[7]: 2020% J* k( Q+ K( f7 h3 S+ m/ I4 e

8 t$ \1 s0 y- hts.month$ h- s# c7 i5 M' \9 l" o: n% ~2 Z
Out[8]: 1. i$ l; p( S2 p) ^4 i
, ?( E. l5 [8 y8 N! @! z
ts.day
5 K# v. _% r9 x& D1 v8 NOut[9]: 1( c; _* r% u# }- e5 U7 t
7 {- f+ y. G6 A" c5 |' \+ |
ts.hour
  y6 |$ e  e' g  Q: QOut[10]: 8, c0 |! n, {* h

& `" S; P2 E% j) R7 [  gts.minute
' K6 e8 u$ [: R0 K2 Y6 WOut[11]: 10
" H+ v* M) v2 i9 T# a7 M) |3 _# T5 p* B
ts.second2 A  ^7 b5 G7 e! d8 H& ^
Out[12]: 30
6 P( j/ f8 ?& ]% A% E# F% Y
* r. `0 X; m1 o5 g: w5 l4 Z1
. }+ K$ p% Z4 K2" d* ~# L( H- @
3
5 S/ N3 m" K3 W& V' l0 l8 ~# d) d; O; ~4
. W7 i6 a. ?) c7 c. w$ S( O5: i' s1 ?  t$ b* F. e5 g8 Z" q
6  G/ b# J  R9 u% ?
7
+ Y' S7 h9 C. w- z87 G+ d2 ?! L4 a0 s7 k. E
9
: P9 O: r7 [- u8 l# @% P10
# n/ S6 {! q/ ?& Z' w11
, T: h# e: w* _% J" P* B* U9 v126 I4 X0 k8 ^! X2 T( Q
132 v# r8 {: c; c
14; y# H, S. Z, W7 g
15
( a( M9 T/ n5 N$ u* \- v3 `7 R16' ]  k( J  P& M' K9 g6 w0 r4 p$ `
17" k$ c2 B+ T6 g4 C
# 获取当前时间
; ]: U3 F( e1 ^. f. e  R/ `now=pd.Timestamp.now()
- g; D* Q1 {4 [1; @/ S. X3 P7 g+ y2 q
2# w. V6 d2 R, ?% F8 g3 Z( Q
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
5 ]; j* D) c+ f+ m; ?3 P! S3 OT i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)2 |+ I0 x8 v, c, @1 h
TimeRange= 7 _& o: }3 A4 T( U2 g
10 3 _% u3 r* G5 ^% S
9! Q! ~7 \. X! y0 S( Z( E
×60×60×24×3654 Q1 t& ?+ S9 Z$ K0 M8 w' Z
2 ; I% }+ O0 ]6 q5 y7 [! J8 {. Q7 \
64
- b9 L8 [# s7 I4 f, |* h; g, o- K  x0 o8 D

  j4 c5 J* b" A- s; c: k$ P ≈585(Years)
( P- d! Z& P8 B3 s
2 u, E# B# y+ X3 ^' Q/ A9 J通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:# g7 G! M# W/ q- f' _

( c3 Q1 F$ H; ]' X. G2 G6 O2 `; Jpd.Timestamp.max' Y* i( N2 |: ]3 N- B( |
Out[13]: Timestamp('2262-04-11 23:47:16.854775807')) l7 z, j! X; [2 s  j6 @/ h

# ?8 T' W$ k# B+ I. f8 ~8 ]+ a( Gpd.Timestamp.min8 B5 l4 j' s5 F' Z
Out[14]: Timestamp('1677-09-21 00:12:43.145225')
$ U; |6 m- e: ^' ?0 u. T+ t/ Z! \* O
pd.Timestamp.max.year - pd.Timestamp.min.year
8 b( D2 T5 h2 Z7 cOut[15]: 585" h6 ]# _+ d/ i
12 }* |% l. s) }: I: o( r
2
) H% [  M* d& E. N& b5 Z! e; }9 R' j3
8 _' e& A* B+ i& H2 d. s8 i3 E! j4
( C6 ~  `+ j( B+ \  i5: n  \% @, Q' T5 b6 [3 v
6' I: s: G1 e, p2 q( f% t( h
7$ A2 B; G5 |3 {! c
8
- r" z' `+ S' a3 J  e# |) H10.2.2 Datetime序列的生成
! K! a5 [& v4 q5 ^! a. R4 Jpandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
9 O6 Z: ?+ ~/ O9 e                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)# E+ w! J" N* B% d9 l
1
, Z4 T' f3 Q$ e0 H! U2
4 I1 h; t# U" d6 `8 m& Spandas.to_datetime将arg转换为日期时间。1 {/ A- X& s( v5 F
2 p3 W5 ~5 @# u( d2 D6 E; |* \
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
4 l' J6 M- E# {& Y5 [) Yerrors:# Y" q5 Z2 `$ B" F7 d, _
- ‘raise’:默认值,无效解析将引发异常' b7 H2 w: s) h' p2 p
- ‘raise’:无效解析将返回输入5 q" w0 e4 t) |( r9 A; \* _
- ‘coerce’:无效解析将被设置为NaT
9 A# B3 b7 |" W+ N8 e% Xdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
, l2 z  l, h1 h) ^8 w+ Y% |5 Tyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
& O) S# o7 y: t5 y3 @utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
# @4 e( \7 E' O  n. V5 Vformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。3 A8 ?* J8 K0 I! [- S) Z8 O0 x" S0 u3 U
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
, ~' u8 r* t4 W1 U4 X8 U6 oto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:5 h- z8 W% o# f' m8 @) `  {
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
! T) W1 y! _  R' [+ d% @9 s4 c# ^2 a" L; o+ K# X
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
3 W0 @$ u7 j! K; j1
7 B. x6 d! J! o) B) n3 f/ y2# G% M3 N$ s) u
3
2 h5 Q8 K0 L, d. ]& Q5 p7 y在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:4 C8 J) Z/ ~' M! ?8 A" l6 {

  M! q4 I7 y! f' Itemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')  V8 m7 u9 a5 ^# F2 D
temp- N* R" f/ Q9 o% N% e+ X

4 [9 z( g* e# X2 E8 tDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)- Q# C5 H. ^5 z
1
& A1 t8 S# W( A5 z2
  o( e, q, E0 t# U' t3
5 l9 m& ?, n% H$ O2 y+ R4
5 R/ \2 F+ L3 l/ t2 a  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
2 ~' @5 K7 |+ y+ M) x
) ^: t0 M' {8 e% Qpd.Series(temp).head()
/ e2 W# v2 H  X* D, A3 t# k
0 E2 S: w! D; G' W, _( H. q  c0   2020-01-01# S  e* m0 w; {1 l+ P0 O3 N* L" P
1   2020-01-03
: [' a6 C+ e# Gdtype: datetime64[ns]% A( ~+ c4 H& ?8 l# s7 ^( I
12 e* @2 A5 O( J$ W
2
7 i1 M8 l/ l* x. t3
, A& m. \: h$ f1 [! S8 Z4' e$ v% i  Y, u! a' j
52 n% U9 A1 L* q: }  l! _. |7 V
下面的序列本身就是Series,所以不需要再转化。9 l" E: K+ f' K7 l' `

# @1 y+ v9 u* J' wdf = pd.read_csv('../data/learn_pandas.csv')
4 b2 F1 ~' i4 V& bs = pd.to_datetime(df.Test_Date)
" n% N0 N/ d: L6 q- u/ H% j! Ys.head()5 F9 x& \/ ~) X3 ?5 i1 Q% k, p3 G

# B* r& e1 H( n' j7 h! n. F0   2019-10-05* `2 T1 D9 a% K& I
1   2019-09-04
; b2 ~% Y/ q# U. h  T% u* g2   2019-09-124 u: d/ X& T/ l1 U
3   2020-01-03
, P# U. ?+ @3 E. R" M5 t. l$ ~4   2019-11-06
& l* m! `# \3 O: T  xName: Test_Date, dtype: datetime64[ns]* ^9 w) w: I8 u5 Y
19 l( k" u; m' W, l
2
+ }+ l4 H8 A  m0 E; v1 Z3- N# a8 e( n: n) O% @/ J( P+ ^' |
44 l6 H6 U! G: y$ m
5, ]  p' L5 s6 v# y9 f8 a
6: r# F: |7 |$ m! s% ?; ^4 T# `
7
9 v9 |' A9 P1 h0 r2 W5 @) n1 A/ U9 H8
- L3 M2 v+ O* ?: p) b' M9, D+ Q/ ~  R$ V- S
10' W4 I# C/ r8 I$ d. i
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:( h# M% `+ G5 ?5 `: K8 ]6 O: c
df_date_cols = pd.DataFrame({'year': [2020, 2020],
. a2 ~& i" f. D4 j$ q  {                             'month': [1, 1],
% U0 C- [$ E& h' V, W: N                             'day': [1, 2],8 C) M8 c1 U8 c6 e1 o/ U
                             'hour': [10, 20],
6 U9 P7 r$ }% [6 k/ d3 S1 Y4 U                             'minute': [30, 50],
. C4 E3 _1 z$ O! W9 G' a                             'second': [20, 40]})" n+ C2 Z# Z' }% M/ D
pd.to_datetime(df_date_cols)
+ Z2 I9 U1 U* _! Z8 {: |' S9 ~7 X: ~7 {" d# d. B: y; L& H
0   2020-01-01 10:30:20% w0 S0 V& i3 |! q& M9 Q2 J/ j
1   2020-01-02 20:50:40
. B3 q! |. p" [dtype: datetime64[ns]
6 f% _8 m; F8 j3 O& ]' W1
9 k7 v/ `+ _' ]' K4 j" r. y2
8 M% N7 i; C! r, j3 m& P36 v! v& O% \5 E; w+ G; T
4( N  x- P1 M) D
5
4 a8 j+ k2 ~3 L7 `1 C) E! l% Z9 C$ V6
) j/ ^: N% \) W6 V# ~+ C7# K4 M' K0 n: J" O
8
1 U5 E) s0 P: |6 h: e# p+ P: U4 y9
& U  O( D, u3 I! v2 O1 X; K10
7 g/ G* ]) I' G7 v! y- G11
; q. z: [4 w9 Pdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:0 ^- ?- f6 [" _# o* m. q: R* q( |
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含. \2 W* @- l0 ?3 W' {1 N) m5 [
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')% z& g* R7 C3 ^. l

8 m" c" u- ]6 Q: [4 R4 Dpd.date_range('2020-1-1','2020-2-28', freq='10D')% C8 D  s2 P$ k! i
Out[26]: 3 L6 @0 J# F" x. K" O
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',+ V: n2 ~- n, D! E& `
               '2020-02-10', '2020-02-20'],
" Z5 {6 _! y$ s1 V: `7 ~3 q/ U              dtype='datetime64[ns]', freq='10D')2 X  c- D2 l- d* N! X

  R2 ~9 F9 P; D: @' Tpd.date_range('2020-1-1',* f8 b3 C' I5 X* N3 J5 b
              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
6 |& G4 a! @6 r6 A6 ]8 ~7 L# B1 b  o- @3 W2 b: C
Out[27]:
1 v! P: `4 W! kDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
- L1 x. E) f8 E               '2020-01-24 04:48:00', '2020-02-04 19:12:00',2 m9 C0 c0 B+ Q2 m7 X' s6 x3 Y
               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],# d; T5 f6 N, z% W0 f/ c
              dtype='datetime64[ns]', freq=None)
3 V, |+ ]) S- r5 F2 }1 I
7 \; {- W. p& ]& q  V* o1) Y. e* c% U5 ^! P. i/ y
2* J3 ~+ H5 Y3 D8 Y
3
, K8 f# v; |. k! o3 h45 Q/ Q: m3 ?) X1 j7 N
58 @3 T) w# y- [' J; f/ r& E
6
1 G* b$ Q5 d+ y7- [* }: h2 s, D7 B& q3 |" K
8
- K- K, j+ Z6 z; I& X- O9
. f8 Q4 c8 y9 H# F10
  U. ]: h+ {! x11
3 W5 J/ o3 i- g8 W) p1 ^* v12
1 w# |5 h- g2 T/ b, w; o- _. |13
- o, O0 D7 v3 f% m& r5 u& o% q149 R7 P) z: A, B4 c* M
15
- h3 f- e+ Y( \/ E& I4 w162 a- u' ~8 b% G/ n
173 Q+ S! [& y1 i8 [+ J; `" z
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
$ m) H. Y5 ]- t. m8 F% _# `& ?1 G
% V5 H4 d% Q% z' {0 Q【练一练】: z& Y1 g# j& V* E
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。2 v% d+ Y! \7 [3 S7 F! S0 i
9 w/ o) f2 ?! s5 K  [
ls=['2020-01-01','2020-02-20']1 H' c. x5 V9 _- n8 W. u: c3 P
def dates(ls,n):- f0 O/ }; R( G9 r
    min=pd.Timestamp(ls[0]).value/10**9
8 F9 V% W7 a4 e) X' \7 w    max=pd.Timestamp(ls[1]).value/10**9
9 T; |) c5 x" l0 W7 _0 P' y    times=np.random.randint(min,max+1,n)5 I# n: ^& x) V" A
    return  pd.to_datetime(times,unit='s')
) l  A% Y+ i0 O$ N6 Ldates(ls,10)
% E6 W% v2 w" x3 B& s+ J5 ~5 Q+ g- L4 @, k4 D8 [
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',4 L( {$ ?; g6 V
               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
  z# Q7 {9 D3 \               '2020-02-15 00:18:33', '2020-02-11 02:18:07',# z8 U' b" {/ e& h! Y" m1 A
               '2020-01-12 21:48:59', '2020-01-12 00:39:24',
# X+ q. q  M% x4 P, V' j               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],) Y' `; Z( u/ E+ k% }* m
              dtype='datetime64[ns]', freq=None)
1 |2 S' V, _% e" y4 r; V5 H1
% d4 s  ~4 d8 S" s23 V, l7 E0 o5 K# L, b3 c6 v. W
30 s. m3 Z$ C- m3 _6 ^
4- F- r+ C: @- T/ y
5! d/ _: N! c- @" a6 H
6
7 K* R% s9 x" ]7
( L* P  r9 O9 z8 T2 t! |82 E9 `0 R  h' O; p& j" a$ S, r
9
! f1 d4 K8 e2 p0 q10
: T% S+ i# `6 R  c& \11! B* [* C1 ]- f) @
12
' V' U, m0 g) s! C- N13; m/ k7 _0 k- i- }3 {. I1 |# ~
14
- `  D4 p2 [4 Lasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:2 r" Q6 [7 h- X& n0 n/ A
s = pd.Series(np.random.rand(5),# N! R+ H9 i* {2 A0 S
            index=pd.to_datetime([: o2 R) P. O# j% u3 d: \+ k
                '2020-1-%d'%i for i in range(1,10,2)])). C9 s# u4 e# t
; r( M  l4 j5 R, u8 y

4 H& Q* i4 k5 X* M- n/ vs.head()4 q+ j# W' N8 a9 `' {. o
Out[29]: ' q5 _: l: O6 i0 o
2020-01-01    0.836578! r: X2 V& E+ n  A
2020-01-03    0.6784197 [1 }* F7 H; \  Z" u* |9 p+ K
2020-01-05    0.711897
) K$ I$ e; F0 g2020-01-07    0.487429" {8 q# k  V& X  @
2020-01-09    0.604705
. k6 @& W! [( w* `. h9 @dtype: float64( }; u$ `$ h- O* Q$ c

* P) ^, x* K2 u& |s.asfreq('D').head()
: g  ~; c) T) G7 m7 S+ {( F& \9 TOut[30]: 1 W  S- O% J9 D
2020-01-01    0.836578- v2 j) Y# n: y. x- c
2020-01-02         NaN
! c6 u- w1 k: M" A2020-01-03    0.678419- R+ {. r" H( a
2020-01-04         NaN% R9 n. ]) c* ]/ G# N5 M% y( A
2020-01-05    0.711897
7 u+ R  J+ W: ~0 L7 l, vFreq: D, dtype: float64
2 a" K; Y$ S/ }2 r" h- q8 \' [% l; F" q
s.asfreq('12H').head()
" D+ H3 s5 t: z8 |( q" i; a' z* _Out[31]:
( j% q; R5 g3 K& |2020-01-01 00:00:00    0.836578
; @8 n  ]( [8 k* z/ G$ w2020-01-01 12:00:00         NaN) X3 c6 I# j, ~: Q6 O* S% t$ ~
2020-01-02 00:00:00         NaN3 y# E. d2 f7 ~% V* x, _& l
2020-01-02 12:00:00         NaN
2 Q/ T, N% u& Z2020-01-03 00:00:00    0.678419' |4 |* k9 ]* E
Freq: 12H, dtype: float64; F# z# `; Z* z$ p% ?. I. {

( }( L* L/ V& b1 o& O: {1* N6 [: S3 l8 o" b
2" d5 C7 J! {4 J& Q
3. @* {. y6 ~+ ^
4
" J% T  l3 N2 d/ V8 B) l50 t; }& i2 H- Z
64 N* ~6 @* M9 n4 Q" k' H
7
$ J& y$ @" y8 a+ {- @/ N" L$ r0 O8
. I" A6 d, l6 y9 ~  @9
2 U# @9 ~4 ], P& G! _* B1 ^10
# f- P# G4 s+ f7 h11
$ Q2 V7 l8 j" a# j" c$ y; M, I12
, X$ q4 h. A( V3 Y& h13
" Z1 I& @# w! |145 N& @/ m( T7 l( t2 j- k
15: Q9 j6 F& e: g- s2 Z& i9 J
16' N6 |3 T0 _* n  u
178 V7 Z" d: Z1 r! Y
18
. \8 B" ]4 }0 Y: W19- Q* j' }; w, o
20
9 [5 L) Q* J6 @& G8 \21
6 e) R4 h0 G: \7 [. Z5 p22% |6 h8 S; f6 \
23; [; j, F5 t2 S) i8 F
24
6 W8 i; H3 a% n, f8 g9 t  {  K25
! i$ J* b+ H, A7 a; S8 v- c, a! D26$ V: s  z6 A7 [7 a. `7 X$ N
27
1 f" e/ n+ m+ G6 S0 q0 e% ?4 e28' Q. ~1 c# h3 s, r
29" x& f  _. o6 W0 B* j4 B' I
30' _* o: k: r- `8 m
316 F" e7 y# z1 w8 q4 R# C1 O- b
【NOTE】datetime64[ns] 序列的极值与均值
  o7 C# ^) X, U& g* F2 Z. m# n3 I) z  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。/ |+ q0 j# f" o+ J
+ B. X0 j1 V+ f5 j
10.2.3 dt对象) j! F7 b  o& v$ Y
  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。; x6 K( _, y( x2 s

7 B. b! Y  s& |, D第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。% Q" ]( N7 n) d7 Y
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))9 {) j6 `; D; F/ }+ X$ s+ `7 J

: Q# u5 E+ K, B) s( Ys.dt.date; \" `( \& q5 T" ^, @
Out[33]: 5 G3 j& M$ G8 u) I& i
0    2020-01-01! h4 ?7 P( _0 H  I8 @
1    2020-01-02, M: A7 K7 n9 v9 H$ f% c" L
2    2020-01-03, m( y1 A% A+ T+ H  J; a* ^& i
dtype: object
% j+ _9 i/ p: [" Q, Q$ F" y9 X" ~; g, `. J* L$ ?) K% g7 F
s.dt.time
2 N; N( s' T5 C% Y  t' Y" J1 N- V/ COut[34]: % G/ u* D, X5 k) R/ B
0    00:00:00
/ e! z: c% V0 u2 N' X& U/ v$ T& h' R1    00:00:00
) V' F$ k- j7 l9 q$ W6 T1 v7 j- K. O2    00:00:00: O. V5 u+ |. G: s
dtype: object* a+ |* a) D# h9 X9 U, T2 o& ]) o
) `' B% b- E* J1 X8 N
s.dt.day0 s6 [) N. d; S" B- {! L, T
Out[35]: ; D2 ?+ P/ P; D
0    1
1 D% d+ _$ g! Z" c, S+ r" T  @6 d1    2
; @( X9 P8 s+ B2    3
! @3 T$ g3 C, G8 t, Gdtype: int64& ^: t, o0 e2 x& k2 w" z, i

2 F6 d7 X1 V! p3 Ws.dt.daysinmonth7 \% S0 U8 ~0 J" ^: B  Q5 S
Out[36]:
  d' I) f1 t! Q! D, E0    316 d3 O) h2 g+ e7 x0 O' b
1    31
8 X5 O& f2 Q6 Y" H) Q' p1 d2    312 P+ j9 S8 a& s$ Y, [8 J$ d1 E- U
dtype: int640 _( G' a; s  A3 d# {; m
4 p; g1 G8 C9 O+ ^( H
1
" [+ _; H( q" K+ C2 l# q3 ?2
3 s# f7 ~8 ~# E; ]; `3
' u9 S: `/ U4 Y  J# d  y6 t* ?+ m4' l- |, |% k+ l* V$ o
5
6 h7 D# Y+ x& |/ s' M! e68 I' `+ e6 i  w1 B/ B( v; N
7
/ K# j" F! y7 B7 e4 ]8
6 U( ^7 o7 W) J4 F! e9
5 e' M4 k7 D4 {# @3 e" }+ u10/ M* |! [# U. R! ~6 R% J/ Z6 I
11
$ B: @7 h1 D4 o/ s+ q  ?12
' u# Y2 }/ U. p) e/ b6 u136 ]! x  c1 u: @# b
14
! O0 R  F2 A! G) N, n+ d, h15* z8 f- h' w  U1 E
16
5 A5 w( b+ J% Y  }' w$ X* ]3 Y3 ?17. V$ ^/ ?% a, {3 J! d
18
% \3 d* E* a+ s6 Y19- y4 K0 q0 Z! r& E$ @
207 ], e/ H/ i7 Z+ C# d9 X# n
21  x  C( t& T% }6 f- L
22
: w. M* M+ M1 h: I& L" B# ~7 h% D$ h236 w- Y0 @* w6 E
24
. O; Z" y+ g! D  B) t4 t- A25* O, a! L6 k+ I$ c! i( i  B
26! ]+ w& F2 X* @/ e3 y5 r8 J* H4 f8 v
27
# L: l2 |. A  |) Q" p: {28
0 s5 `9 c6 U. w& ^290 }% W1 F# }% G5 P
  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
: ]' ]- L2 c3 a8 Z# O7 b/ k6 Q( o) T1 `6 y0 J
s.dt.dayofweek1 ~; Z( B! z" Y1 f
Out[37]: - V0 ~3 E! }" M" T1 k8 z0 W" ]+ |% y
0    2, ~) W1 J( @0 Y/ N3 A
1    3
  y* T& e0 n$ A0 P, v0 Z$ x4 Y$ M2    4, r" D% q3 E& g7 i
dtype: int64! L- J6 L  a( e' A2 L
2 o: O1 U5 p; [9 ~
s.dt.month_name()
: V% W- K1 u/ p' iOut[38]: 5 |& V# I9 q- i5 B* e
0    January# u5 u7 w( @4 H' k
1    January
* g$ ]4 s4 z& P3 n9 ~7 O% y8 p2    January8 N8 }8 n. C, O& \& b# y1 ^% v3 v
dtype: object
7 {7 C; x. R! h  R2 N& C
  F# l5 s* N' D2 Ps.dt.day_name()4 K8 x" t; z/ w6 ?  [
Out[39]: 0 G! q. i4 i# a1 H
0    Wednesday
" e  M$ Z: e" \, e% X1     Thursday2 k0 x1 ~' L5 V" S+ K: X* i! j
2       Friday: D1 Z/ Y9 f1 b* D" ^  n! t/ W
dtype: object
0 P% r: I) H; s9 o4 m+ R( R! q% G- J% \2 T; o- k" J8 L
1
. b% a( S; Z" m1 V! g4 c3 {# o5 k2
) _  G; e: a1 m3
% c7 q6 s. V/ G+ R: k4 U/ O4
- N; H; E) p2 y* z+ w5: R3 M( V7 y) s' j  X; b
6/ Y- o* ]% o8 \$ t( E6 ~4 d
7
) K* }; u" k  J7 @- V4 L+ A9 Q8
* |  w$ x) W* ?9 g, q" S$ K# s% r99 m3 H2 |) p- p- N" L# @
108 Z0 \& R9 z, X6 l3 o" d: T+ o$ Y
11' s$ t& C5 v  `, j. A
12
1 w! ~  `: ~/ |4 O8 e13
  E8 M" y1 j! B4 S5 I4 j14
6 ?# v4 m" I8 P/ S15
) c! t: |- \5 W6 e: R* K. P; ^$ I16- E5 r/ @1 w7 }2 u0 o. }
174 F, T- t6 ?5 [# Z# Z' D
18
" J  O4 i) l6 a5 e9 E! d19" u% t7 _; k$ z- N7 k, a' b/ y$ ^
20
- f2 R9 J; z) u  T# S6 i$ h第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
0 b; E- j! H' ?6 D4 T; c. hs.dt.is_year_start # 还可选 is_quarter/month_start
1 S+ m% x1 O" pOut[40]:
7 i! C3 h2 Q/ e& V  r0     True
0 A! n3 F* v, |) K: v1    False) k1 Q+ S' q8 d, S; m+ I
2    False: d, P4 [/ P# u
dtype: bool( Q! ]; c7 G' _! d' C
+ i! M8 ~; H/ Y9 c/ W
s.dt.is_year_end # 还可选 is_quarter/month_end# Z7 M$ m/ w/ T  A0 d+ r# e+ k
Out[41]: ) L# ]8 X- @8 S! S% F' i" Y6 N4 A7 ]0 Y
0    False4 q* T: l# l# h4 w+ x; i" ]
1    False
! p5 b$ y6 b# \$ C! I% W2    False
  I3 z# _9 k( F( a. R$ R$ t/ w6 ?dtype: bool
+ b* L! k: l! b; p1 I# u1$ k# {4 D# f: d) K: y
25 T4 y1 |( Z/ [, {; L! l  o. K
3
8 }+ W3 j# g! O" A! ~( k5 Z4- o0 Z% p: ?$ S3 r2 v- Y+ Y
51 c5 x' K& J% x& R) N! `8 ~4 a
68 e4 M' q2 }5 _% p3 _+ b9 A7 N
7) a3 z! s( A* Y+ B2 m: C0 i
8
3 G3 E6 }& c, u9! w7 G4 j  A; v1 C
10. ]5 ?& I5 S3 q- t
11
+ x' T! n# p3 s5 u0 T3 N5 W1 z12
; N5 x% p2 v$ w# Y2 {13
3 P' i. L+ B) Z' z; ]! ], D& F第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。1 l4 |, z) c' Y+ [  w: r! Y! H6 [" Z
s = pd.Series(pd.date_range('2020-1-1 20:35:00',4 r  W2 b4 w& `7 e# d) B& M& s
                            '2020-1-1 22:35:00',7 w+ p  x* i; g  n+ O0 e
                            freq='45min'))( k" t! v( i$ P; v+ Z. Z

7 J# F$ U/ b6 g3 a" x' t- y- I, g. Q
s- l# n$ i) K6 V/ ]% I
Out[43]:
! M7 E5 |3 f( V6 _5 A1 u& u0   2020-01-01 20:35:00
. E- c) ^1 {! f& e+ d1   2020-01-01 21:20:00. z; M  g5 g* g# T
2   2020-01-01 22:05:00- y& o+ p9 y- C- c
dtype: datetime64[ns]
# }3 q7 |  s" o4 i/ N2 R; A- n+ g
s.dt.round('1H'), I% j. |6 N* L
Out[44]: ( X7 ?* I) z; x7 {4 x" A2 ^9 Z$ k4 |
0   2020-01-01 21:00:00' B0 A( e. d( g3 R
1   2020-01-01 21:00:00
! Y+ G6 p& ^2 Z2   2020-01-01 22:00:00. p9 B% Z) @" n. g* j
dtype: datetime64[ns]8 e1 M8 m$ H* q
1 i. A- I. O4 g3 c5 H
s.dt.ceil('1H')/ y) J4 ]4 W) b" N9 i- X0 {
Out[45]:
! R* i+ B8 D/ h/ s$ K: r+ |) y  O0   2020-01-01 21:00:009 n9 W: ]! h  s  e4 k" Z7 n8 L
1   2020-01-01 22:00:003 T& g5 ]' n8 T7 T1 p  o
2   2020-01-01 23:00:005 o. }, ~5 ?4 l9 v* F
dtype: datetime64[ns]
1 y( }- L" o/ h* A- i! f+ n  M7 g
s.dt.floor('1H')
8 w/ f# m: c/ U, r* ]Out[46]: $ o0 {0 S4 w& i1 N4 j0 U3 s& g. O
0   2020-01-01 20:00:00# n2 m( G; J% i  A
1   2020-01-01 21:00:00( E5 B# H6 |3 O) c) ?
2   2020-01-01 22:00:00
6 C2 U) U; ]; S! o, {% Adtype: datetime64[ns]. a, G" v, {8 x$ W# M0 t& u! i4 \

; t- y* g+ e: L8 I! s- d1
5 p) W$ |  l2 O; e6 t0 T2; |8 R- I+ p3 f5 \, w6 u( L' o. \
3
' H# O% w/ W% k, n/ P3 v42 M6 F! Z8 C4 T2 k: S5 j6 l  R
5
) a8 b3 E" g0 H1 q9 O3 t3 }6/ ]1 T# ~: H5 `, Q* `2 A
7
8 x4 m! t& L# |% ?. r8; K7 L- q/ d% Q$ W! [
9  r. Y( B- y& ~6 W4 _5 e  i
101 ]0 \, Z( R8 e: ]
11% ?& X8 V& W# h7 S' G& E' s
12' s6 `. [1 A8 d* E6 F1 X
13, K% ~/ `3 n% g' h- v
14( ]- Y- T/ y1 p
15$ v7 Q, H9 Y- z/ e* `( \( l
16  ?( W7 V$ E0 |9 g, Y7 U# B
17
* n7 t$ _0 `, M5 Y0 Q$ h18
0 {# X- |! {) \' P0 I; r: P19
6 ?2 x5 ?+ T' W3 T9 c7 Z1 Z' m208 w/ E9 R. m/ W5 X. e1 @, e6 ]
21
0 T" T( k- ~$ m5 F) T22) p2 ]5 U, r3 l  W: e0 P
23- R, c2 \- O( ~7 X& m
24' P, m* ~  {9 t& X! l
253 q& r! ?' X: A6 X$ l
26# |1 @* N. e* z9 r4 _  @
27
' _2 W; ~' f: x3 O* {8 @0 A28
* k7 X- G, A* ^6 h. p29, q5 l) H' f5 d$ h8 p8 L- v. F
30' _7 ^# p" W- O) b" Y
31
# X( @2 B+ }! u. N# `& a& d32$ H, i/ ?+ `# y
10.2.4 时间戳的切片与索引% m& |. G" `$ k+ T, p1 c1 X
  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:+ d1 G- C/ G7 b8 w. b
9 A+ ^8 y8 t0 T% p
利用dt对象和布尔条件联合使用
  |1 G5 ?4 [) t/ [利用切片,后者常用于连续时间戳。
2 O/ N3 m% k, v4 n8 M7 }6 i8 Qs = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
8 W3 K5 @3 a* h8 c% f( r( b$ Gidx = pd.Series(s.index).dt* N- y0 ], H+ e* A6 P: W
s.head()
5 }" o3 Q3 h, N6 c2 ?( {* b+ y7 }
  Z( v, W+ y5 }8 G2020-01-01    0
& I: R+ \3 m1 Y' v  Y3 x9 g2020-01-02    1$ @5 R4 e4 n# P2 i# c  D0 l* c0 V* X
2020-01-03    1
; s% k# v0 e( J" r1 Q" f$ `2020-01-04    0, J% d8 k0 }) ~4 b
2020-01-05    0/ s4 j2 W8 p/ t: t5 q
Freq: D, dtype: int32
$ {! R$ [) Y4 v7 Q% _+ I4 }1) ?/ {, @- H; _/ z
28 T, A( j1 F+ A) [
3
, I2 B3 n+ M* p/ G7 o8 e4; s5 N* ^; m  w9 p
5  f- J6 R$ h- Q1 H- G$ u
61 C6 l, J/ P& c. j5 P4 k
7
% ~! B7 @9 V  \( d8
: K. C; X  C7 d9
, K, C8 s) G7 Y. N4 w10
/ q, ]" R, u" E) gExample1:每月的第一天或者最后一天
8 `* P) P/ E  L! j: U' I( E$ [* r0 r8 L0 ~
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values8 H) Y9 M. n2 f+ b) I! n8 }
Out[50]: ' o3 G5 ~# D" W9 A* f5 a
2020-01-01    1
7 J  l- f: y; a2020-01-31    06 S8 R1 Z) ]7 O- K  ~  T. s
2020-02-01    1
) M, [' G5 S5 B( u0 Q2 E' `2020-02-29    1
( |* K0 l7 I! m2020-03-01    0- F( P- m# D+ n+ _% `6 y0 ?
dtype: int32
' d  L6 _- I  W! v7 ~! w& D1 k8 L1
* }3 |( n1 B+ ^) ~: z1 e( w9 m( n2. J& e0 B( g  M7 F) r. I- c
3
4 |" |$ C+ n9 h  N3 d1 p6 t# o; {! [4
9 m: Q- L! S6 O) t$ L) m+ Q& u5
* G( P1 g/ b6 V. s6; K8 Q* ]  w; `8 q* x8 t% C
7* L6 a- ?* A4 d0 v4 s& d  {: }
8
) |( I( r( ?" F1 W) \3 I  JExample2:双休日
) I0 a, Y, o8 L$ \- `& D! x  @: w2 n  |1 T. |2 n
s[idx.dayofweek.isin([5,6]).values].head()  t0 r4 Z# D: i( q# N
Out[51]: & U* u+ f5 X/ s0 }+ B  Y* R
2020-01-04    1
+ ?1 a; J+ {  ]" X2020-01-05    02 _  x% c2 b) a  D
2020-01-11    0
  r# c" X7 m1 _6 X; f2020-01-12    1
8 l) y. ]$ ?. B% w! V0 e2020-01-18    1
4 q# D7 t1 |- `! G. |dtype: int325 S' |; F( J" _  s# y9 e
1
6 Y* T3 b$ ]" X2  k+ [! ~& S6 U2 _# V* H
3
4 M- K& V  U5 C6 N6 [4
% c- Y4 {: R6 a5 y  m5$ O/ d) G/ V8 m& U9 K
6
( i% [3 n( G+ |7 ]4 ?7
/ F% O7 i4 S. N# {! Y8
& t" M* Z% y8 a$ _Example3:取出单日值
/ r) O$ q  D: u( }
4 M' P% N+ ?0 F" Os['2020-01-01']
( P7 x5 M9 ^$ u" g4 _8 c/ e# uOut[52]: 13 _0 O' n* p- a) g9 F

8 ~, @6 P# T6 \- I' n5 k; O/ K3 ss['20200101'] # 自动转换标准格式
/ Y& K5 z* \" xOut[53]: 1) C& p' o( M% f, l- p) m
1: R, r- i! K: f+ O
23 t" ?% M1 g4 ~* ?) ?0 m
3) c3 T7 @) V& y! j- d
4
; F2 R! T! D: d5: J1 F, t1 V; O/ |5 Z$ [% h5 M. Q
Example4:取出七月
& T) _6 W& f7 C. b$ B- L. p. j  ^5 @1 m6 S8 ?" {' y
s['2020-07'].head()
7 q% O) @* Q" G) Y6 Z9 K) ]! w1 ROut[54]: + g% w8 x# @  `8 X- J- x0 X
2020-07-01    0
2 t/ P% L9 Q3 a9 {( i2020-07-02    10 k' s( g% p6 _) [, e3 g
2020-07-03    0# @) n" M7 ?) ?2 v+ G
2020-07-04    0+ O5 J: j  E3 E) @3 @: ?+ g
2020-07-05    0; p: S$ r  A  i- A3 b$ A6 S
Freq: D, dtype: int32
9 _/ R0 P" |: L+ E1
4 C5 K* d8 o0 y! c& `% E( R2
4 b; i9 R) f# o! B; t0 w1 [33 @; h, O& d% \% U6 W! m
4' B! q, a" a+ \4 J) s. i. F
5
* X7 V( i4 @- N' W2 K6
8 \5 E4 h- I! }) ?3 G: H7  Q2 Q& Z2 c; c
8
, }5 O3 C) n' u) v( EExample5:取出5月初至7月15日& J2 H. T; A, n* \

( K, K9 J4 H5 @s['2020-05':'2020-7-15'].head()
6 B0 U$ j0 ?. L( C: Y) J7 p7 ~Out[55]:
) N. |7 k# a6 p0 k2020-05-01    0# f  `2 d8 Z- p% Q
2020-05-02    1/ p2 K7 I" K' L; M
2020-05-03    0
( {# S" x. N: d! \. V- T4 l2020-05-04    1
+ K  w2 N- u7 Y8 ~4 o+ S) |# f2 ^2020-05-05    1% Y! n  _0 n' z$ ^4 `8 m' t9 A
Freq: D, dtype: int32- }$ e0 f7 f1 |7 ?3 k) W0 }
$ G: Z5 W( G: `- ?& m
s['2020-05':'2020-7-15'].tail()
2 h, J; I! b7 t; n' k/ X) LOut[56]: 7 P. o5 f7 a/ w, p6 `6 b" G8 j  |: H0 [' N
2020-07-11    0. n" x, E( w6 B3 p* d
2020-07-12    05 [. H) I4 o+ c3 j( I% i
2020-07-13    1
% o: P# }- `- e- c+ G1 M2020-07-14    0( F/ Y1 u. S- o+ `7 o" k4 W2 d
2020-07-15    1
( o% F, N* P& }Freq: D, dtype: int327 p* @' O7 i! T
5 m3 c7 f8 x6 _5 a% K) v# }
1% \9 P4 ?4 E2 L
2
* R9 f( `; P. A) H; s3
) A' j; \& [' v7 W- q4) p# q" j9 |' a! n: z
54 k! B6 a  o7 H' [
6: r# }( B- m  m: x- H3 }
7' i% p. U- ~$ Q9 l8 R8 |+ W+ V
8
' ~* f) L- M& p' u, y+ T* M9
6 K: r+ X- c6 w8 o  w/ G10
7 Z2 Y7 ?) R6 Y% c8 h$ j11* O8 {" V% h  A
12. V7 _& W4 ?* T) t
131 E3 O! |! V* o1 Y
145 Y! G  R* o3 j# L
15
( C/ ]* k3 Y: A/ G* u- x; P166 g& a7 L; W* R+ a
17
& U/ L5 _1 P7 F. P  x; y10.3 时间差0 c5 G$ @. h2 u0 O: C) |! h5 @
10.3.1 Timedelta的生成
5 X7 ~6 H5 \* ipandas.Timedelta(value=<object object>, unit=None, **kwargs)( [" J0 d# _- a
  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。) w: }+ k4 ^8 z
  可能的值有:3 E2 l1 O: e& R' p6 t

' Q- Q  E( E- L& t0 Z* r‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
# _3 A3 B1 U; ~) E# d‘days’ or ‘day’
9 \* x2 z& O7 M8 |‘hours’, ‘hour’, ‘hr’, or ‘h’& Q' ]/ G8 I/ a# Q) A4 J% E6 p
‘minutes’, ‘minute’, ‘min’, or ‘m’
( P5 p, L) L/ t6 a8 W‘seconds’, ‘second’, or ‘sec’; v' b( @* e( p7 k# S. ]- k
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
; \- |& }- Q4 s, C微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
4 c, N' ^6 U9 T纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.# i' q: ]# h3 e8 J7 U
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
: u! s- x* C) R! E. q' p6 Spd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
$ e8 N8 l" `5 OOut[57]: Timedelta('1 days 00:25:00')$ G% f: ~) Q4 R4 k( T' k
+ t; k% r7 k3 [2 d& t- _
pd.Timedelta(days=1, minutes=25) # 需要注意加s  e  k: `5 Q) [& t& j
Out[58]: Timedelta('1 days 00:25:00'). ?2 n1 x$ t; ]3 a7 @2 L

. C( D" t9 Y* v; f+ a5 Z8 _. Zpd.Timedelta('1 days 25 minutes') # 字符串生成
3 N# [- n4 U7 z: U- M" f- QOut[59]: Timedelta('1 days 00:25:00')
  g6 e3 J6 ~1 V; M* `2 I( Y3 |& t
' k  S; X) Y/ B  b( Y$ opd.Timedelta(1, "d")
) _1 C$ }( N3 V" w3 [( d5 mOut[58]: Timedelta('1 days 00:00:00')
: B) I" ?* M* D( n" a: w0 j- Q10 L6 ^8 R! ^$ a; y8 r$ `
2
+ X2 w* d; V) s4 z+ o31 r, L2 X5 ]6 D; V6 E8 A  M  u& u
4
- o! l1 }) G$ {5
" [9 }5 q2 }6 X$ o: V6  Y5 ~! p7 W2 L4 D! ~9 E: I
77 q6 J( v7 B$ f5 b% y" x% B" H: \- c
8
+ F- l% n( a6 T9' J# }$ ~) @( N5 g4 @, ~
10# J: o" b. J6 F$ d- `2 [, m5 q
11
8 T7 K: x0 P2 R  ]1 U& ^生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
2 P3 a. s3 `% `# Os = pd.to_timedelta(df.Time_Record)& n% F0 C" o8 b* v8 G+ r0 p
+ b( d* k$ `3 k( r0 j8 v1 W
s.head()
6 d4 k. E& z$ ?  AOut[61]: ' ^' {+ w- c# F( ^
0   0 days 00:04:34
% b4 T2 X% E. V  g% n: L* ?  U1   0 days 00:04:20
8 i8 b  J% c; Q: U' e4 q- x2   0 days 00:05:22
/ r' z8 @$ s% u# U3   0 days 00:04:08
: s( f" v0 j3 O# T2 J4 J& r, [# k4   0 days 00:05:22: {' i3 z8 b( n% O
Name: Time_Record, dtype: timedelta64[ns]
3 \. ?! J! D# t& _/ y( s1 S1
5 s8 e; o3 s8 A" C. k4 C2
% e- r, T/ u7 i1 |* P3
1 x) @" }* V4 u  a7 z! Z4- S0 U3 o7 m3 W
5' C# K' a8 V' U6 J+ C$ d
6
8 y9 o+ x& ^$ h. v/ X2 b0 ~9 d7
) H6 a6 V0 I$ D8
. b; |+ ~: n6 J1 h7 b9
, I' }* A% E5 v% T2 r10
# x% K  m1 c: t! N8 H  L0 \与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
+ X) `# H/ A4 @# o6 P1 apd.timedelta_range('0s', '1000s', freq='6min')
. ~- @" c! C9 r6 S# v8 QOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')+ a8 m% b6 @! c$ {  E9 f  q% k
& f6 O& M* l" X2 F+ y
pd.timedelta_range('0s', '1000s', periods=3)
4 }2 o% b; H0 r$ {3 COut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)8 E1 M/ Z4 C; E" o1 c! W
1
8 ?* b& Q2 }6 X- d6 E% x2: ?0 y2 @$ g5 k
3* K3 B8 a; ~. S5 q7 _& E7 q8 Z
4
" p7 M$ H, y* T, H8 s5 y5
; L+ |- r3 d  J* m对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:3 X! F9 k4 g+ i& V+ b& [) V0 z
s.dt.seconds.head()6 h: v" K- ?$ `" m% ?
Out[64]: % e8 J" g- Z8 {" t( s# V  u
0    274
, t9 H0 z: e8 {$ i1    260
$ r( t) L  _8 Y5 M7 Z2 z' w8 [2    3220 O4 O# v5 _7 F. h( F/ m) n
3    248
  O' H2 e( n4 S5 M2 J! X4 W3 @4    322( C2 k5 ^7 D  ?' R( {
Name: Time_Record, dtype: int64
; l; O% b+ M8 j0 P% K; g" }' D+ h11 n7 W  ~! i6 h- ~+ ]5 ~3 M& Z7 |
29 r. B8 v9 W4 W% Q& X! `9 g4 E5 ]
3
2 ]0 \+ C, j+ D9 r- i: M& a4
0 @( i  k) \5 H% e8 A- U# Z! x5
; C" j: W% I. @' ?4 A" c6
4 W  l* z* ]) E! J( K4 E7
8 T# A! k8 B# i+ h6 b8
, b* d) f' ?& U如果不想对天数取余而直接对应秒数,可以使用total_seconds
# P8 m" ~' O. X8 u# U* F* r3 t) i1 M2 F1 L
s.dt.total_seconds().head()
3 u, d4 c- a7 @Out[65]: , G; t1 ?: [% X5 f4 p# A" A
0    274.0: X' y9 t9 B, p  D5 X" B
1    260.07 I0 v# A) [- a* O) w' }
2    322.00 p/ I) b2 C9 L) A- w
3    248.02 v' U' C* O! l% @) z5 @
4    322.0$ \; E" I6 X. e
Name: Time_Record, dtype: float64
- R* q4 i) L5 ~  U9 l6 ~1
/ x3 ~" F) G& u& k' P5 d22 n9 i! K, ~1 a; a8 V3 w' g
3
, A# y8 S5 Q# y' O% J0 q) U) a0 f4# t+ p; H  t4 E. l/ Y. ~
5
; O8 s: x9 ~2 z( c/ P6
% m+ Z1 `, K( t7) Y, S0 W' @: E" }. ?3 \
8! e; P# v; t: o* q& R9 N
与时间戳序列类似,取整函数也是可以在dt对象上使用的:
- y" U. N- I  u1 A1 j. {2 H3 n* a, `2 N, a9 K  V9 t& v7 k
pd.to_timedelta(df.Time_Record).dt.round('min').head()
; d2 O7 ^' K+ ?1 r+ r- E7 hOut[66]:
1 a3 S+ L7 o" ?$ f. G0   0 days 00:05:00
/ J& G, g( m/ G# O9 V7 {! R( |# I1   0 days 00:04:00
: M* \0 u* U5 A2   0 days 00:05:00
* ?, t5 x, l7 H8 G, o3   0 days 00:04:00
% V6 P, |/ `0 a4 H4 V/ |4   0 days 00:05:00
4 s3 S$ o% B4 p! hName: Time_Record, dtype: timedelta64[ns]
& V8 E$ Y! T. ~( e1
. F( B" j* B. P! V$ R2+ j/ `3 ]1 l* O3 l- W
3
  }1 a9 y  P) V8 M48 [' I# u) [- v& \9 j9 G1 s* ]' X; P
5
2 E5 N( V/ Q3 h- A# q6
7 O% `. p7 j* |+ U& g6 `+ l( N7, O9 C( Z" Y6 U  r" ^
89 W4 Q7 {( J! n# `* W0 T
10.2.2 Timedelta的运算7 T* `; O# q$ V1 [
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:/ k1 x& R% l. `
td1 = pd.Timedelta(days=1)3 Z3 U0 X! Z+ G/ Z# H
td2 = pd.Timedelta(days=3)
& H. q1 Y' W( j  z% p: wts = pd.Timestamp('20200101'). l8 O5 H( [" [; g- |
) B: `- \, o* w6 ~
td1 * 2# m! F7 w; k+ b; I% B( n& o
Out[70]: Timedelta('2 days 00:00:00')
' ^7 R6 p( k0 N: @
3 F$ ?  f; t+ ftd2 - td18 r& w9 L7 F- j! e6 \
Out[71]: Timedelta('2 days 00:00:00')( o. D: h- p& U, e3 s+ i

* N! D9 ^/ B& vts + td1
0 `1 j' d: t; G5 oOut[72]: Timestamp('2020-01-02 00:00:00')
6 C+ Z2 ]6 U+ [3 `5 ~# H' m% J1 \
' P( `. \: m6 t- K4 o- nts - td1
" R+ K5 N. i* N+ D3 WOut[73]: Timestamp('2019-12-31 00:00:00')
$ r2 S! Z0 [  D( T# N14 y. v* G' A8 r. G5 ]& {$ L+ [
2
, u2 [8 ~7 ^# V0 x, v9 i! P6 C3
6 F1 R& W: R. _+ p! \' K# W' o4  H! m0 H- b/ i1 E4 P, s
5+ G1 k  d+ d7 h9 |' g% c
6: O% I0 v2 V; y  O* H0 K  U
7; R- O) q1 m+ v+ {5 ^: X! W
8; W3 I7 n7 ?/ n* H/ N  {
9
' O/ U* X3 x9 ]2 h. w10, P+ `1 n" }% j
117 u% o$ g3 V1 u/ a5 P1 Y* z, [
12
! [+ \0 {. s" k- x' Z13
/ H8 ]' R  }3 q% H- ~( [14* N: y2 a! J; [" _1 M- o
15. e- C2 g: d, d6 y+ I7 `: x. p
时间差的序列的运算,和上面方法相同:
! A) U/ `6 W# d& i" f3 btd1 = pd.timedelta_range(start='1 days', periods=5)
& X( C" [: U# F/ htd2 = pd.timedelta_range(start='12 hours',# K* w9 R5 d* r; `/ I
                         freq='2H',% ]! ]1 e# [" F+ n3 \
                         periods=5)! ~# @) H- t, w
ts = pd.date_range('20200101', '20200105')
  Z* s+ ^4 l) Q7 J4 K3 p) mtd1,td2,ts
% v1 n: k& C! C& z
& Z7 I" h7 h/ }3 W4 M' |TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
* |* R3 F& I1 p# X! R9 pTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
3 n' C) A9 c3 {5 i                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H'). Y; H8 r) \0 d% m4 _
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',/ {3 c6 m1 G/ L9 ^9 s
               '2020-01-05'],1 I( b- }7 z' w
              dtype='datetime64[ns]', freq='D')
; d. f9 \+ Z6 Y9 M; @+ e/ Y15 t* A4 K: a% y0 d# O2 A, L
2
6 v. h" c1 e/ O6 Y( ~; [4 l30 b& T/ C9 ~  p2 F, g
4
  e# t; `, Y* J- j. M2 T; Y6 L5' S5 ]" p; I* W2 A1 l$ |# J+ V
69 O. Q3 L0 W8 {) ~+ |% w
7
% k' V8 n( m3 a) b2 ?, Q8- y2 A4 f5 B0 k' i$ k* h) Y8 z# Y
9
. A8 l4 m3 l" S, z( R& X" j  h10
. _( ^, {3 }3 r4 f( X11
# T- D5 P( _" N! `0 r- Q( N1 |12+ U, F' G7 Q6 ^. I) N
13
; t& j0 b4 w. t9 W8 a  I) Ntd1 * 5
/ H1 }  W; }2 {3 }* |Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
  [+ V6 J) I. ?# B, s. L! k0 y! q1 q5 M0 G: F: f6 _
td1 * pd.Series(list(range(5))) # 逐个相乘3 T, u+ J6 t# U" J# [* W- F: `7 a
Out[78]: ; X6 ?' ^, ]8 l9 c- I
0    0 days. b& G/ U4 F6 L5 n' V' \/ v& {+ z
1    2 days+ l5 q2 K7 v! M  y
2    6 days9 h. K# j2 H9 t( b. J% |' |
3   12 days
8 k. @5 g' b  E0 T4   20 days5 G) ~) `' I- `/ Q' U, ?: U
dtype: timedelta64[ns]. H8 y3 q& E) x

% t; b7 v% i% i- A1 std1 - td2
  E) `, R' S) w+ qOut[79]:
9 L: e) O' ~0 y7 v) G" PTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
) D3 Q* `  W5 |% B' ?6 L& S                '3 days 06:00:00', '4 days 04:00:00'],2 K, ^0 |$ a/ S% X" \* J9 Q$ j
               dtype='timedelta64[ns]', freq=None)4 y+ O6 ?7 t2 R" i$ V& \

3 N$ r+ `; b& Q1 V6 ytd1 + pd.Timestamp('20200101')$ C* i1 J' Q8 C, W! @, r% A; _
Out[80]: 0 z6 T2 }; k3 _. n' [
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',, u) X1 Y  w' N% x, v
               '2020-01-06'],dtype='datetime64[ns]', freq='D')# [" s% Z2 L. H

3 h; w8 c6 g2 G$ |% @# Z. ktd1 + ts # 逐个相加
. i, _1 f$ K7 J( \2 s3 w$ m7 nOut[81]: : d% M" v. I; @7 \0 P9 m% A' d& B/ q
DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
0 x5 A( b$ D6 l+ d6 G7 [+ Q& L               '2020-01-10'],( A! B* P4 R, m
              dtype='datetime64[ns]', freq=None)5 s- g/ c4 p1 V" P. w; z
5 c  N4 J/ e* r! @" `
1
) H: I1 W% D+ c5 B% \: |, ]: y2
) B* r5 ?) E& U) F' U6 v3
  r$ _! Q% t" V$ G( i6 N4
9 P* F& c0 O  h) @" k5
7 ]2 X! Z+ z; c: S/ A  [( m6, j, K9 \- I0 r6 z: ^
7
- e) T2 E9 f, o1 |- l8% f# B2 E( F; O  v. d4 n
9
/ J+ {8 Z; V: y7 Q& V; y2 S( O; z% G8 K10/ h& _! w$ A& |& l
119 `1 O+ l; `  a4 g; W* i
12: X% p- D8 r8 i" |+ o: I2 V0 d
13
6 h, k& i0 w8 n3 n3 q14
$ j% x1 f# K. N7 N- `154 t; q0 I' D; R( s
167 r4 j6 p: f8 x) p. U6 F7 U
17
- Z  M# i& ]( m& y18
/ I1 N5 o  C- ]19% T, ^2 @5 L# t$ u3 K) P9 I* S
20
! g' N1 O. X+ N- m. J21
* `5 X! C3 @: Y6 Z* Q, {22  h- d4 i% y* d' n
23
$ ^. z5 i' H" J, [# p24; K' _3 o2 y. n- q9 W: k
25% C( N) E8 \% X* }( `. q3 `5 h- o
26
0 g4 @; y& S: t# B7 n; `5 A1 U5 E27
8 m+ ?% K+ K+ ]9 F6 Q! G% X' j28
! X9 K8 ?: o  H. v10.4 日期偏置
& F2 T$ j! o$ S3 j1 X  S2 P2 }10.4.1 Offset对象
# x, T8 g, `/ J3 K- n  日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
' C0 [5 m( Q6 ~4 c3 Y4 E+ y) Z+ ~- h4 P
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:5 e( v1 m+ p) z# n+ l3 o. O) g

9 o4 f$ O$ l7 N; z4 O/ t3 S: }s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本- |5 B& ?' B' e
s.kwds:{‘week’: 0, ‘weekday’: 0}
8 C; s2 y- L5 d4 Zs.wek/s.weekday:顾名思义7 ^3 q, y1 X' q+ c1 [) V
有14个方法,包括:
& _# Y" k6 x) H( m5 s0 `: e7 W( U' j$ X
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
1 @: \0 `& W" o5 c4 d! Rpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。1 e! C, W2 E3 N  Z* S, `

4 B$ j, a6 m  |9 Q* h; W有两个参数:
; a) p$ H8 E0 B; R( @, u$ k: P7 fweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。1 g8 e# x3 E/ _
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一): o. Y2 c& J# C$ {& y
pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
* E' B; V7 |2 n3 e* H. K& f2 _3 H' b6 \! C/ v: ]" G
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)7 z! H8 n* f. P
Out[82]: Timestamp('2020-09-07 00:00:00')
" m3 V$ u' A$ t# F+ x9 n8 }/ o5 F, }: g- e" j, |- H+ W
pd.Timestamp('20200907') + pd.offsets.BDay(30)
3 W# C& z/ ^6 z8 }1 d% Y+ p" rOut[83]: Timestamp('2020-10-19 00:00:00')* |3 j; [- Z, \2 B  b3 {
1
7 ~. T% [1 O$ T/ v27 F+ }7 p! V7 m
3
6 D, \6 c4 o, S* ?2 q  J6 u4
( [! ^4 j* R" g5& _6 k- _2 `3 O
  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
2 ]/ V0 z' w( c5 s: M  A' W" V/ ]
* ]4 B7 Q9 }4 X0 r" y2 }- `+ jpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
( G6 y6 |4 m, _. ZOut[84]: Timestamp('2020-08-03 00:00:00')7 W* A& ~( o; p+ c6 D: n

$ _) @1 N# E" l/ j8 t9 v$ |. apd.Timestamp('20200907') - pd.offsets.BDay(30)
' G! B# h# t/ _+ m# X0 {1 LOut[85]: Timestamp('2020-07-27 00:00:00')
. j7 q1 n1 q4 H
) ]- \0 ^0 m& Q$ [pd.Timestamp('20200907') + pd.offsets.MonthEnd()8 e" B2 [4 g( }* U5 N/ r* u  a
Out[86]: Timestamp('2020-09-30 00:00:00')
" r7 R6 S9 j7 A- Q8 ~1 `! ~5 g1
9 V1 ?: H1 x; T* _! z; S' c5 d; B2( ]" ^: }5 R# ^& G
3
! j! E! {. F. i0 ^# r4
* @1 u8 `, g8 p, `' z5
- @! }/ x  H3 v6( k" r  H+ W. ]2 J+ J6 d
7
/ |* N  v& L/ r. \' v2 w8
& ?! k, Z# B- @. T4 n/ R# P/ X  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。5 C8 Z) O- I5 X; b% n. R
  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
, p, t$ [- x9 u  L0 g9 A4 J5 L$ k& e& h* N. _7 x* I* o) B
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])  n7 P: ^% O( {
dr = pd.date_range('20200108', '20200111'), d0 d" v# q! T" h. X$ [) v' K

/ [" b6 V0 l6 wdr.to_series().dt.dayofweek
# ]4 W% D4 n# v0 V9 ^Out[89]: 0 s+ J2 Q& Q, P2 _) J8 g: P  g
2020-01-08    2
- z  |5 W, W5 L! H2020-01-09    3
, I; U# Q5 p4 D3 j- F' O2020-01-10    4
% U* ^# O% A- f% |: d2020-01-11    5
+ w) y7 B: J1 m/ W8 V$ E1 W4 xFreq: D, dtype: int64' P6 M7 M+ x' b( j% d
# ]; Q- m6 d3 e7 o5 Y
[i + my_filter for i in dr]
! w7 X' h' I; }Out[90]: $ O( M" `1 e% C9 A% u6 k, J
[Timestamp('2020-01-10 00:00:00'),* X9 N- K* D$ O3 f  e- W
Timestamp('2020-01-10 00:00:00'),
' d0 d9 S/ A. g& _# b  G Timestamp('2020-01-15 00:00:00'),
( e' V6 E4 s$ e$ ?% d( h Timestamp('2020-01-15 00:00:00')]$ b1 ^8 ?3 k+ B1 c" E) z7 y  S& e

! ]1 F  }  A$ m4 v1
6 L# l8 H1 {/ [* N2
3 ]% F0 n% s/ i' w& w$ B0 v9 m* Z3# P7 M$ p5 l6 ?
4( f2 M, [- N2 q4 o% I  {
5
! o9 d* e* Q' M60 n) ~2 w+ W; h% I8 m
7
9 L; U8 u7 \# B, {2 I+ W8
. \. I& c; `$ N8 b/ P) l- R9
+ z) G- n# }  J# I0 D10; m% \9 [/ L5 R' l& h% k# A
11, l7 G2 ]# B& N$ V. C4 v% t  D- F; {
12: }3 X6 u# c: \0 A7 b9 j
13; K1 D' ]" _4 a2 A
14
5 _( N' L8 S+ I0 c- x152 s4 X5 g5 j7 v" ?7 Q6 P3 v
162 z! v* F% S% k# i* u1 t4 L
178 D/ }; x! j$ ?( b- N
  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
4 ~4 L8 t6 \0 w# g  T* r0 O
4 {& Q& B& K2 k# j# Y【CAUTION】不要使用部分Offset% F+ [2 L, g9 k! ^
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。) k; K" U$ S. W; `2 q4 S" s

! i. w* _2 S% ]8 V10.4.2 偏置字符串
' y+ `; }7 T& Y9 M# L  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。1 Y' b- T- @+ Y& X
, z$ [6 u. [5 Z( f% P# B/ @
  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。1 c2 ]) z: w/ A# ~" I6 m! C- Q

/ o5 u4 p' R! }$ Z5 lpd.date_range('20200101','20200331', freq='MS') # 月初. u/ t, k, H% _+ p: q+ g
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')7 a2 q6 O' k9 m' J6 G: c* f) ?

- ?- {  l6 t' a) A  q  Vpd.date_range('20200101','20200331', freq='M') # 月末
& |) ]' A: V! z6 J& i& pOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
6 \/ e% h* Q6 k$ s( [# f/ b
0 {6 t% v: U6 i# s. I" d8 Wpd.date_range('20200101','20200110', freq='B') # 工作日. v4 ~( X/ A+ g. X5 `2 Z" n' ?- z
Out[93]: * J3 |0 ?% [# u0 i& _
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
4 U( f8 H9 d$ L% s5 |4 I) Z( a               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],9 {' l# t3 h3 P1 D4 f2 g( g
              dtype='datetime64[ns]', freq='B')
  v9 T0 Q$ b' S2 s3 [8 p* I8 k1 `4 i2 O$ u
pd.date_range('20200101','20200201', freq='W-MON') # 周一  v6 h3 Q; z5 _! ~! d
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')4 @1 y, L5 t. d3 l) k. U) t7 ]% ^0 n

8 g( N; a  p+ A+ `pd.date_range('20200101','20200201',' u0 d4 H  h( I5 _" k1 k% R
              freq='WOM-1MON') # 每月第一个周一
8 e. s8 I4 ?# V5 F  x! s
5 r: i, h6 @1 D1 P7 A  _) jOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')5 ~; B/ @' ~: L" U. n

: m& c9 m% r2 x# _7 E1& s+ y6 X; k. l$ [+ |
24 K! _' l. M2 `5 L2 _5 C7 V
3
7 M& O1 `* s1 R9 f# D4, K4 a/ _5 D3 A" h3 t! {7 T/ K- b  h
5/ H' b' I: ~2 C6 I6 B/ ^
6, Z& _! f* ?( H5 Y
75 _: e# T; F' \) t9 u2 e" }+ H( {. S
8
4 P. T* A8 l/ U2 w9
' U! Q. K# d. B  T; ^+ E0 R( J100 p$ }" D6 R; U& x
11
, S% D8 c' W9 w- d12
; [5 o- L" w! w8 |( i/ e  |0 h139 h8 T" Q! i. O- c4 }
14. n$ A. _- r1 ?2 C, t9 w
15
6 ?( J6 ]8 m) ?/ h163 E1 y: V( L: h* b( Q
17- I; T0 n, d( o# M5 H
18
! x* \' o; p% G0 {) ?# l, L- l198 W4 J. N2 [" \1 o/ o
上面的这些字符串,等价于使用如下的 Offset 对象:% g9 }5 t' e$ z0 D& e% W. a
1 A7 W5 w! ~, h, n( P
pd.date_range('20200101','20200331',0 c. `3 S+ z6 @3 q8 o
              freq=pd.offsets.MonthBegin())5 `5 V3 z  X$ l
. g: q4 V2 I/ d' F( m+ N
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')) {+ k( q! ?! o" v& @# u- K
( x. b% A  }" h) e& P
pd.date_range('20200101','20200331',
1 k, {, e# b' k8 t              freq=pd.offsets.MonthEnd()). \7 B" [( a: B/ B1 Z
" L0 R* t& X% ?, {. N# b% P
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
7 ~7 a/ R0 P9 b. r- j& z7 X; H2 T4 p) n
pd.date_range('20200101','20200110', freq=pd.offsets.BDay())2 t# w% s. m9 X
Out[98]:
- Z+ |& A* b* \2 ?" d# [DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',: ]# Q) `/ d+ ]) J, Z* O! W
               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
4 \# r* T! x7 |$ A              dtype='datetime64[ns]', freq='B')% R) V& a* M6 ?' r, T5 z) ?
$ O- h' B8 u+ n: B; B
pd.date_range('20200101','20200201',  j* g  @* W5 p0 r1 o, i$ y' J
              freq=pd.offsets.CDay(weekmask='Mon'))% ~  k1 X; K# H/ j; d4 I# }: Q
5 F3 i# p3 e- d8 Q7 \9 x1 C4 k+ N
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')- x# K+ m1 Y+ g: I# P! `& r

2 H  N! A% I7 x/ d. ~% B$ Jpd.date_range('20200101','20200201'," A9 m) m9 O/ n/ t, W, G
              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
6 }+ J! ?* P0 k. v' [+ K5 e) i+ w: q6 m! n7 v
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')9 p% u8 `# p+ C6 w

9 Y+ N( w8 w& h1% u& N/ M2 t7 a
2* z9 ?; C" {; p
3
3 O3 R3 L3 [. j0 k% a4
% S& K# i6 B1 V  V  @8 y: `5
; ?- _/ g# p( V. p4 B& p6 ?6% ^( p7 Q- d" e! D+ Y# |2 M) T4 p" a( k
7
8 f$ |6 d9 F2 t8
# @7 ~; t. i8 m% D9! n  ~9 J$ T# Q: _4 o
10
& V; c4 v) G# t' W11
1 j0 `, E% v- E( ]12
8 o0 r4 Z2 K/ u. a7 @134 @0 d; R- Z$ Q& `5 `- R
14) X4 V3 y0 f7 q
15
& ~# g7 F+ H/ Y9 }, P; W0 v9 z4 v160 P4 E# y& P4 `2 w6 ?
17
7 D2 X, w& v, _. w18
8 e! b4 c, w. b19
+ c! c0 `/ i7 I% s6 Q  w3 t' c0 s20
, n; I, `$ {5 t* j/ ^7 g; ~21( R% ^' E! m! P& M! N) N
22
/ n: s1 n" M; h23' }, N2 o' H( z
245 ~- [6 y$ _6 k% Y  t; T
25, r+ d; H$ L& m7 h9 f. l; T  `
【CAUTION】关于时区问题的说明
0 |. C% j* p: ?  Y8 L" b  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
6 E( N  W/ n7 C5 v, h) q4 D! w' }* H3 s, \$ O
10.5、时序中的滑窗与分组% |- W. d! J' Z% O
10.5.1 滑动窗口7 s* N2 o: _: \7 x# }
  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:6 c& e+ g# k% H4 u

- a  n3 c9 T% Yimport matplotlib.pyplot as plt
* H/ U7 j' D: \: @2 Didx = pd.date_range('20200101', '20201231', freq='B')
& f8 ^4 X4 z( W! fnp.random.seed(2020)
( U8 @- n  T  D4 n
7 b& \( n0 n) B1 B  Kdata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
. B1 N) \( B5 L6 X9 U( R+ b# ls = pd.Series(data,index=idx)
! W3 O- O( X  U' Z$ }' }, L3 Cs.head()' _; ]1 r. x! b1 }/ z5 Q
Out[106]: 0 k; D' u- I$ e" o0 W$ V" T
2020-01-01   -16 T1 W, V3 W; a5 |2 W$ i
2020-01-02   -2! }# @% F9 {: S6 }, E  Q
2020-01-03   -1
* \0 Y! D- j+ w# k. A0 C2020-01-06   -1
$ }: {9 k0 M" D6 l2020-01-07   -27 h1 A( _# R+ Z  R6 W
Freq: B, dtype: int32
" X$ d- d1 B8 P9 cr = s.rolling('30D')# rolling可以指定freq或者offset对象
) a" f$ a: Y) r: G" f) I
  j- }+ J( Q) G9 T3 ^" Oplt.plot(s) # 蓝色线
. ~' ], y. W# L- n- I3 o/ A! dOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
! p$ E. B. o. Kplt.title('BOLL LINES')1 Z9 C2 n! U" ]  _- v9 ?6 z
Out[109]: Text(0.5, 1.0, 'BOLL LINES'): q5 v. N! _/ D6 B7 ]) r6 c, O- l

* O8 [# y9 ]. g4 qplt.plot(r.mean()) #橙色线: W. b& j% _' A' x
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]3 b. ^# ?* w# a( J0 i

3 Q, e5 u7 |4 ?plt.plot(r.mean()+r.std()*2) # 绿色线5 c. [8 r9 V2 Y0 U
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]: Z$ Y3 C0 W/ b' b3 X+ b
1 N$ Y: J5 T% \3 Q) h2 {8 K
plt.plot(r.mean()-r.std()*2) # 红色线
8 Q- N" |6 P6 R5 HOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
4 G3 Z6 b  A; Y" v. x4 K5 K9 S- d1 V
1
- U5 J( Z. L  P  K2
6 y. z8 U% H9 ?$ i3+ c0 X7 e/ f$ C
4
, n' I1 ~1 |2 n- c5" N- ]1 t6 P( }4 l' K# S& r4 k% S6 W
6+ M, _: ~# @1 J8 W
76 X1 i- ~$ d; @+ ?
8
4 ~( G, S0 t3 j/ Z9
, x  q; _# c; J5 L7 u) h5 I' D! W10
  Q: ~- T  [4 P) Z' C; `- U, L11% n0 Z- m4 k" g' \3 [
12! b6 B9 h" v4 S
13+ P: P; z, p8 o( B. R
14% `5 V- I6 V6 b
156 @4 C- }. i8 j' H1 {! G1 z6 f4 d) s
16  k3 b' m2 e# U' j" |  m; }7 q
17
) f0 g: G3 L5 B18+ B' ~, `& s# n1 r% ^% @. }
191 J2 j$ [0 {: W1 z/ z* r
20
' e5 P) \: q7 z" z3 u3 v  h" S21
. o' O2 C. `6 p; g1 B* ~+ N22
+ m: F% _9 ~  a8 k) l: L" p. p23; ^3 q6 v1 _7 t- ~% f
24
; ~( |! K5 B7 J( u: V+ K25. z$ j6 S2 P4 `
26
- t8 ^9 ~. o7 M# c( B+ T! b27% p- X2 Y% P* _. ~; {4 `1 h
28
7 L* }1 p, o# x* S29
2 E: ], L$ w6 c9 S: Z$ [) w7 ]! J3 @  R0 I, |' v% i
   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。" i( B( u- Z5 |: n9 V  U
   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。  \; L* E5 ]3 Y: q7 W
+ a" F7 m; W  t& g7 g
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
4 J. J) O$ I  _& P/ n. gbday_sum=select_bday.rolling(7,min_periods=1).sum()
/ ?/ g. W+ _9 g0 ?# Y2 A5 q# lresult=bday_sum.reindex().ffill()* q/ @* ]( ]5 J
result
. Q/ V/ o' R* K. Z! p0 F0 I% p- m) q5 X4 G& [: {$ I/ L1 J; ]/ M7 p( h
2020-01-01     -1.0
; }8 w+ H7 J2 R: L. p2020-01-02     -3.0
) b1 \+ M- F' p2 j. Y3 ^2020-01-03     -4.0/ ?2 D3 Z1 @; u4 @
2020-01-06     -5.0
; D( }3 S, q/ ?2020-01-07     -7.0: O+ p3 A# e+ F5 B+ k
              ...  * w' e5 \9 |5 J
2020-12-25    136.00 v6 w% O* Z( t' `1 m+ r* t1 N
2020-12-28    133.0
1 T7 h4 k1 Q% B* ~6 `* S- b, s1 H2020-12-29    131.0
: A0 G* m1 _: H2020-12-30    130.0% r2 w; Z5 ]; l& S- B/ u
2020-12-31    128.05 X$ r. ]3 W# A/ Y+ K
Freq: B, Length: 262, dtype: float64  m, H( }4 `  e3 Z; {  v6 t3 Q

; N# a% U7 V1 y$ a1
& Q8 H. H" W( c7 G: ^28 X  j, @9 a  s: E
3: ]$ t. @2 s4 Z( Z2 P" L9 ?
4
  `& G0 v, ^3 G9 y! S' b9 O5# [4 F) P7 Y  B9 c" U- O
6; x" b' w5 a" S# b" r' U
7  W* L5 f2 U  ~7 {
8$ ?: I) K5 ^2 l% k! o
9  M' |/ Z2 q/ Y) ~: \
10
/ O$ a8 d4 p6 A0 G% p11* `6 e9 y4 S1 t
12, M, B. ?2 k8 f$ C
13* ^( h- ^: h9 ?+ Z
148 c% n- [5 l7 h* J( j& |# W' m& B  G
15# P- v% u0 x; [6 k
16
+ U% _- S8 Z, _0 B177 v  V( p, d$ Z
  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
- ~7 y# g) g7 y( ~. l7 S4 y) s+ Y5 t" s6 z5 B
  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
3 ?% X2 [, ?& e% Q" _
* `6 B7 n+ Q, S8 z3 Es.shift(freq='50D').head()
' e5 j! k2 l+ s. aOut[113]: ) V0 c+ `" v$ I: G& v( O
2020-02-20   -1. Z2 O7 p/ I0 n9 O* b
2020-02-21   -26 h' S( W4 T7 W7 B0 x
2020-02-22   -1# Y$ n( m$ ], V  f+ u. y5 ~
2020-02-25   -1
4 S. ~8 f- ?3 ^% S/ A* w" Q6 K' U2020-02-26   -2
+ w# u4 J$ U# X9 [, H# @' rdtype: int32
+ R; w) M& c& @1 j' Y1
: x! G& u0 V8 |0 k7 t  ]/ w' h2
0 ?) L3 F( d  u6 b3
" k) ]! _1 l  F! V% a0 Y" F. R4  h4 n) M- O: {) R
5
) M9 F5 d0 z6 a2 @6
! g* t. n3 L8 q6 l- z4 G8 ~7! J4 U, @$ W2 i: r* ?
8
5 J4 {4 |' g+ E- O4 ]" G0 k  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
# A& Q) `6 u& u+ A' K! m  Y0 U% ~) D: A" i( p3 [* [0 [2 k& _
my_series = pd.Series(s.index)* ?6 G, Q) ], Q. z( r- Q
my_series.head()
# ^. p  A4 _# \5 C8 g4 a7 {# FOut[115]: # ~, |9 r( ~/ a
0   2020-01-01+ w* q0 ?! ^: b3 h. [
1   2020-01-027 J( w$ k. |( P! Z, B* t
2   2020-01-03
6 W: |& P& I$ H, p. P, I6 Z3   2020-01-06
1 _; y* d, B9 h4   2020-01-07' E# v4 S0 _4 Z/ N/ Z  |
dtype: datetime64[ns]
  U& m! e0 W7 Z: a6 G- l! ~
1 X) b1 R2 v4 C8 E7 _my_series.diff(1).head()
8 x; u: Q. S6 W* O' iOut[116]: 6 k  S9 P2 w6 X( S
0      NaT0 e' r% _+ T- T  v* v
1   1 days
. K& i% d. M9 [- D9 u6 q$ v' q( \2   1 days4 p7 V9 m8 ~- }# f* Z& H
3   3 days
. {' v3 P; a. Z# L# u4   1 days0 R# u4 C( M% O" |0 w5 ^' [. X5 L
dtype: timedelta64[ns]  ^" a  |8 D! F9 Z1 Z' [, n

; j, c6 p4 ^! y* y4 p7 m. a13 ?1 {: R1 F, _  X
2
) x' F" L" k& F/ x$ L3
+ w8 J# w9 D+ b2 _; [. s1 n$ R4* l7 g& s" W5 P' X7 i+ @- O
5
* A0 }# X0 G6 ?& T4 F# d( o9 k6
5 Y$ z, g3 }- S7 S% L( e7$ m1 d* q  I6 T  o% q9 [
8
  p5 @" l8 Z! o% p  V9 Z9
7 {. A: i8 P  x9 K) {! R10
7 k9 b2 j4 ^0 L- R11
# ?  g3 E3 e- F/ E$ `12* X( j, Y6 {6 h
13
; f* e$ N  k5 c7 O4 J/ D: x: x14
$ \* b/ R# m0 O* m4 w154 I0 V5 ^% ?$ Z& ]
16
- f: T, k) m1 I3 ?17
% t, R# r+ L8 X$ |18
: p+ i  u! O( y" r* K4 S& O  ]; ?10.5.2 重采样
( n& n# U) d' b  DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)2 e+ Z" d: [% v- B0 v
常用参数有:
8 ?: w1 `- m1 K+ ?8 b! v9 Z& }7 {. S& p' p7 F% k: D
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
* x9 Z* C. a3 e/ l- _& Zaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样3 X( t' s) W" f8 x
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
) @1 }4 Z6 o- R9 ~( t" _/ \label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。' Z* d3 w0 w  j# K9 o! j
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。7 a( q* Y% H* U! b# k8 m1 ~7 D
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
3 H) ~: G9 v8 {! W2 ?: llevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。+ ~% O; h+ N1 D
origin参数有5种取值:
4 _- Q; ]& P5 o- ?7 z5 c‘epoch’:从 1970-01-01开始算起
: H6 o& G9 D( J7 u5 R- g‘start’:原点是时间序列的第一个值7 ^) H) L/ A2 R
‘start_day’:默认值,表示原点是时间序列第一天的午夜。
$ G& M3 G' s" V6 k+ S$ M'end':原点是时间序列的最后一个值(1.3.0版本才有)
& s2 I- B. z% C7 \6 ?- _‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)  f) c/ s5 Z2 e
offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。* P( M3 r# H  s- ^
  closed和计算有关,label和显示有关,closed才有开闭。7 j5 r- Q! }$ F: }4 W! i
  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。+ S5 N& o) W$ O  u1 z$ U
8 U3 ]7 u  F. E) K6 D6 L
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:# X+ h3 f3 N, G/ u: e2 ^8 Q2 r; l
s.resample('10D').mean().head()
! O2 H4 L$ v- S$ s5 j0 h( IOut[117]: : i7 ?( Q1 ~( P( x( l
2020-01-01   -2.000000
, O$ j4 ~2 ^1 K( e& F  h2020-01-11   -3.166667" B$ p$ k7 ]+ q1 C$ H* `" ?. T! W
2020-01-21   -3.6250005 C  `+ h7 p; Z7 J* K6 T
2020-01-31   -4.000000) E0 y% I3 u, r6 d- J; s- E3 f: {
2020-02-10   -0.375000
+ j& ^, [& ]2 C# P: ^Freq: 10D, dtype: float64; w, q! Y# H8 N
1( l8 K7 \4 m2 B( A4 f  _  a$ p8 N5 {' F
2
4 T4 R/ f' L1 W3 q) A3
2 p0 L$ Y5 s' {/ R/ p4 G7 ^  N4
; a+ {1 x7 G! E4 x% _& [" t5
; M( ]8 L3 L0 O  ]# k. B6. d4 x- T; E& y* w5 w
7
" j3 T3 _' k) k0 d! C7 s% M84 ^; P/ R# V. R( T- p
可以通过apply方法自定义处理函数:: D2 k, M  r- T( r  A
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
" C4 o6 v/ @) @% m1 A. ]! c* Z2 l3 r1 _. B  C, F  Y
Out[118]:
/ R7 o$ C" n% u5 I) r6 K2020-01-01    3+ I& r$ |3 `" Y% p, `) i
2020-01-11    4
+ \4 q0 q/ a) Z( [6 [2020-01-21    4
! O% H! H4 D' y4 U2020-01-31    20 T8 [: ]5 Z% U. d, L8 ?' N9 B
2020-02-10    4( d8 T4 _4 w; Q1 z( f  J
Freq: 10D, dtype: int32& [! L0 K# V2 q
1
6 j) `' X  H1 e! W1 u1 v; k2
1 Q- y& S* J7 v& J7 \. I/ K7 [3
7 a' R4 K  R+ F/ g/ Q, h$ E4
5 m6 X9 n* Q7 i% z3 o0 {' G5  N3 y4 l( [4 h& D. i% d- p
6
6 L+ i' E8 i% ?72 y( _- B# X1 I2 [3 _6 h( o0 H
87 h" @3 U4 Q/ ]* b7 ~
9/ H; W9 ~! G; j* O0 C" w
  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:8 d" r- Z5 T; I, q+ m
% t/ D' B% b  Z" S# |
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s'); B0 [- C2 n2 ~5 s5 \% M
data = np.random.randint(-1,2,len(idx)).cumsum()7 r* `- @9 g7 r/ \9 Z
s = pd.Series(data,index=idx)' G+ a7 a9 R6 m/ b; E6 U. J7 A$ H1 W# [
s.head()
/ Q* |4 J* M8 q) S7 u6 b: |3 i: w) S9 b4 }: E5 H' G( K* i
Out[122]: , J! W9 u/ [0 H* e" ~
2020-01-01 08:26:35   -18 z) A: c3 R8 L: t) r9 a
2020-01-01 08:27:52   -1
% a% `8 k$ t0 P2020-01-01 08:29:09   -2
# g: \( p- p8 \2020-01-01 08:30:26   -3
" f8 n7 Q8 ]2 h( l: j1 f2020-01-01 08:31:43   -4, W7 ]7 g: M* K9 e
Freq: 77S, dtype: int325 [1 i" h( }+ f! w9 O
1
& K5 n2 R7 ]( b8 w# p* H3 F2
( Y  ?9 U4 l% r5 g* T+ A3  [/ ]6 Z" h* |' P# E
4
9 b2 c0 Z% f/ ~5 D- n# m+ q50 Q2 B1 M# d# n" V" \- G, S9 {6 Z
6
3 |9 H% m* v2 V  d5 H$ }3 I: V6 L2 A) ]7+ W" v1 Z, C. h. C* ?' Z
8) `! W9 i4 I+ V/ Y
9
# `% D  P4 c/ H# b8 [4 X: u. W10. a  ~% l- q# M9 |
11
0 R! U3 \: U; b( [0 S7 M6 B12
5 |0 x; s( z" v; ]% P8 v  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:1 G9 ]8 z9 E  d/ _& c7 Z0 d
' g& ]) O) Z/ }- z9 p, {' p* D+ G3 H
s.resample('7min').mean().head()
' v( |3 [; F( Q: o; ]- d7 {0 @# ROut[123]: 5 P" r4 E) r7 D4 j# v
2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
8 H1 M4 W& D- M; c! j# \" U2020-01-01 08:31:00   -2.6000008 |1 z. Y6 G# m, U! Q; Y( ^
2020-01-01 08:38:00   -2.1666671 w& c8 V$ R: y: z( z5 Z
2020-01-01 08:45:00    0.200000  E0 X3 V8 F# D9 m" i* P
2020-01-01 08:52:00    2.8333336 W) W" Y! Q- Z4 u- H
Freq: 7T, dtype: float64! H/ M1 l- m1 x" R- z+ ?
1
  w: K7 p* T! e, I/ [2/ m2 H$ ~' l0 W! k
36 h' m8 |! s, W: N
4$ X" N* K4 L$ I) O$ l; f
5
1 n% ~+ j0 E1 W2 ]$ u6
: R, x/ C2 Z0 D. u) f7) F6 }$ O6 c: r% ]% ^% J
8
! W+ V- }  X0 }) V, R/ v0 ]7 m  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
0 X6 l; ~. ^4 R+ x5 D! x
0 C+ Y1 p( |8 C( F( X8 Ys.resample('7min', origin='start').mean().head()/ ]1 y6 {- n7 g. w
Out[124]: , Q5 ]7 J% Y/ V* c2 {" o
2020-01-01 08:26:35   -2.333333
. r5 n) O4 I, E6 J& f& w$ t  K2020-01-01 08:33:35   -2.400000
8 S' o; X# ]: P; U2020-01-01 08:40:35   -1.333333' L9 e$ b- ~6 t+ C
2020-01-01 08:47:35    1.200000
- R- G# Z9 G+ j5 g2020-01-01 08:54:35    3.166667; \; z2 t! W& ]" u4 m5 U
Freq: 7T, dtype: float64: d9 L$ U9 [. o% {
1  Q1 c2 _8 F- n* I
2
# i9 \8 ^9 p7 P: Z4 v30 R, i' j% @' }% z' K0 W( W
4* b2 h( l: P' a3 \+ [9 n
5
3 c3 j7 G4 {) X5 i& q0 Q, @6; f1 p9 V; Y' o3 w6 o/ M0 i8 h
72 K) n' e: P3 M/ }8 y1 ?4 G- |
8
& Z) }+ ^# g3 c/ z1 j' d# t  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
/ s" o% `1 B- H# {9 J9 y8 R6 G3 r' l! `" _
s = pd.Series(np.random.randint(2,size=366),1 [# W5 S+ |' {4 K) W: U
              index=pd.date_range('2020-01-01',. R. O% e1 c/ p$ a
                                  '2020-12-31'))
" x; P- G% W6 D; U" k% t% T$ J
7 p. j, l! F6 Z% Q# F( y! k2 \2 f( H! j( A/ e! ], |
s.resample('M').mean().head()! z. P! j+ _. D8 V$ ]
Out[126]:
# O+ }# [! i% V% k+ ^! p# v7 L8 |2020-01-31    0.451613* |+ J& |* _) d
2020-02-29    0.448276) g: i4 P" f& ?! f6 l8 r5 ]
2020-03-31    0.516129
) c( V0 }* v2 H; A& \& j$ N2020-04-30    0.566667
& ~1 W- e" c/ F  V; C5 f) s0 M) W( f2020-05-31    0.451613: m2 @8 R/ h6 ^
Freq: M, dtype: float644 l8 p8 E% p; r. u9 Z( \

' l( E& C* U6 }# B& Qs.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
8 v" C; a' b7 ~" `0 b6 L6 @Out[127]:
$ A, p- c4 y8 }2020-01-01    0.451613
3 d+ Y2 @% b! V1 H2020-02-01    0.448276
% A: \7 y4 k2 [3 H" E# `% K2020-03-01    0.516129
" z9 f- w' {6 g) O2020-04-01    0.566667
1 G+ z# l2 @/ K5 W% H5 l/ q( n: \- {2020-05-01    0.451613$ u) Q0 h2 h4 V7 M
Freq: MS, dtype: float64
- q6 H) J! ^& B: \( z1 T
6 a3 M% o8 ?: |15 g2 E+ u% ]3 K6 q  C3 f
27 W" m  L& E7 u8 \: t0 U; V
3
% }* }' U. D7 Y. J4
" w& p1 i( r8 T! F' v5 e9 V5
, d+ a( T& i7 h6
4 z' j6 N1 |" S0 x7/ E8 t# B# n4 B" K( X
8
) w0 K' ?0 M0 Z2 W) P9
: A0 W6 l4 Z7 h6 l3 G: \& m' j10
! N+ @/ K- Q8 M11: D4 Q( J  w! v' K  s6 ~5 M
12
' ^6 L  X2 Z$ ]' p: Y0 [13
" `: w; z7 v. \& c2 g9 t14! A) m! W, s; o" p
15
" h6 ?( v1 m9 a* s  k4 u16
0 f$ C8 B& M. B3 Z17, @4 P) N6 Y$ ?/ g1 @
18: C& F7 j; b* j7 i4 [
19
4 [) X/ z- y( ?' r20+ M. @6 @* R( ~
218 a, ^9 ^' |" l
22
$ M# F. j& U* k* z, V* }, x" z对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
3 d1 k6 u" K9 ^d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
9 p/ z$ c& t) i' F2 R6 x8 Z     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}7 T( g0 w" q1 l8 H3 @8 h
df = pd.DataFrame(d)
2 g3 B) s0 H* Z+ A% vdf['week_starting'] = pd.date_range('01/01/2018',5 I% k: T$ e6 p1 M8 m% T
                                    periods=8,3 ?0 I: ]" W( T2 r
                                    freq='W')
* }3 {9 g5 A; ?df1 m  k( i& x5 p: V- K, N/ e
   price  volume week_starting
2 K  Y- A, ~! L3 l% a1 R! ^0 q( k0     10      50    2018-01-07
' U7 ]+ P/ }7 m2 m1     11      60    2018-01-14
+ U* w8 {. P6 k1 `2      9      40    2018-01-21
$ ?0 Q8 Y; o, R2 P3     13     100    2018-01-28
4 \2 L5 _& J3 |4     14      50    2018-02-04
' ~) W9 o+ ?0 U' e6 ?+ ^5     18     100    2018-02-11
2 T* g% A8 r- y( D6     17      40    2018-02-18
4 I0 s. d* D; u4 y  ~8 F  A7     19      50    2018-02-25# m  Z4 J; P& h! P
df.resample('M', on='week_starting').mean()
, N- a! G- h; W9 E               price  volume% Y3 P1 ~( D7 ~' J* u, Y
week_starting
( h! i8 R: L$ [+ C& C2018-01-31     10.75    62.5
! y- q0 F6 I& H2018-02-28     17.00    60.0
  S: O, V& X' a! j9 g0 Y0 S2 ^
& k4 q4 R% V9 q$ T4 ^0 ?- ?% u+ L1
8 p/ ]. }9 A( }$ o& a5 N" Q2# j( c- B5 T4 p* K6 }! t
3
2 I. G0 A, ~9 c6 l4 i4$ s. L% L; E7 X7 w, V
51 m& s6 G) X8 O  k4 d
6
8 K( t% W7 ?' V% y; Q7
# K. X" Z  I& y8. m% G, x7 P$ P: W5 A4 M
9- c. k0 V- v! b% o5 y+ q
10- ~; ]! G) N* B6 F& W! I' f1 [
11
9 e  |# P; Z6 ^1 S" S1 n3 f* N12% G8 {& m- I' Q6 _7 o
136 g$ ]( d* G& [. d2 Q
14* Y1 [8 x1 O+ ?2 O/ _0 P9 a
15
& `) U1 z3 u0 m% D165 y- c; E( D! |& b( l6 X
17! K/ N* w2 l0 `9 S3 x
18' p0 |9 e5 B3 y# _* `
195 M" A. j+ K: M! `2 C/ s; l% l
20
* y  L) J& ^  f; k21
; L; [) y6 E, w2 W- i$ Z5 I对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
4 H' p; v5 S4 p8 a& V% cdays = pd.date_range('1/1/2000', periods=4, freq='D')$ s; w3 n" g8 K
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
, i4 T. L% p" t6 V      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}% }% H5 S  I4 \5 V: p1 W
df2 = pd.DataFrame(
7 J' F; c7 c0 ]) g    d2,
0 i( q2 e, z: p( h# k- f    index=pd.MultiIndex.from_product(
' l$ X0 o/ ~5 t' Y& C/ {4 N        [days, ['morning', 'afternoon']]
, z( m4 w, F' q1 c4 d& ?; w9 [# {    )1 s& z% O& U0 Q8 z0 f1 t
)
2 G: v2 {% `3 C# bdf2& }4 U' k& f; y( H1 _0 Z
                      price  volume0 {% T1 [8 h+ }/ D& ^  Q
2000-01-01 morning       10      50& J, O7 L6 {/ g( J, r
           afternoon     11      60' `, r" q% }' O9 g( Y# I# k* Y3 A
2000-01-02 morning        9      40
8 p" f6 A, D/ @& F5 Y3 o1 q" A: Z; w           afternoon     13     100
5 n' ~* Q1 J8 N% g/ m2000-01-03 morning       14      50
" X3 g5 ^% {7 k  s- w+ a4 H. o           afternoon     18     100' v0 Z; k2 a$ V: k
2000-01-04 morning       17      40! B2 N$ ^" O9 e# K* ~# Q2 u7 k& w
           afternoon     19      50; `; O) @: ?2 A* _* Z3 ]
df2.resample('D', level=0).sum()
; k! ?. h( u; m4 m+ ]$ P            price  volume4 E/ ^2 {; `) o9 i
2000-01-01     21     110- A' o- S0 J) o. C7 _% g8 E
2000-01-02     22     140
# d! @0 q" J; z$ d0 y$ x! O2000-01-03     32     150) i1 f4 H! G8 b) y: Y, U0 ]* p0 ~  F
2000-01-04     36      90
* N3 b* {1 g2 ^
3 ~! i3 D3 Y: Q2 s1
, Z$ b+ X3 r1 `+ T2 F+ w8 G' u2; [  K5 y! M* K
3+ ^9 x# A- e% A
4- t! ]# t) S% ]) D; U; H
59 d1 r' h: B% G1 B; g! n6 J; I( w
6$ T" P* z3 H5 T! F6 y% R- T7 h
76 p" [8 _$ Q; ]# ^
8
, ?5 W5 ]# H4 M5 [1 a# j" Q+ z99 W- B5 s" u! X# b* B
10
( U" _! \: m7 U+ Q1 T7 E11
7 }+ p- N) m( z2 `9 x* g12) f( L' X( O- m+ |  ?* u. @! Y
132 z! y3 {; S# c/ Q' J
14
7 |* f8 w$ c0 k5 Q0 V15
% m$ V7 n1 m$ K# z8 H2 ^' I  v16
3 q$ `! |. p0 {: j17
, y4 T' ?4 H: c* x8 V4 N18( Z. c5 e- i+ O8 T! h1 x
193 p- s! l6 l+ h( w
20
- C6 a" P  i# w0 ~+ l21
3 M4 f/ a1 K3 y! Q! G0 v22' ]: C' o; W9 a) r7 G6 L
236 k7 q% W! d* J) n2 \  ?( s) x
24
! E& c' v: ]) h- M25# ?( [' L2 ?* i" U
根据固定时间戳调整 bin 的开始:
1 Q, u; v+ I4 P+ J% qstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
' e3 v% H! K8 f7 q$ n9 ~rng = pd.date_range(start, end, freq='7min')
; u4 g3 e: r: xts = pd.Series(np.arange(len(rng)) * 3, index=rng); D- D1 [: N! j& V1 ~
ts5 [& z# s) w" u- _: e
2000-10-01 23:30:00     0! M7 {% s, V8 q/ p& ~! [
2000-10-01 23:37:00     3
; L! a* W' L3 _$ r6 w2000-10-01 23:44:00     6) H7 e1 R* q' G, v
2000-10-01 23:51:00     9$ ]6 ], U; F  i& \
2000-10-01 23:58:00    12. ?; [) K  T$ ~7 N- v9 J
2000-10-02 00:05:00    15+ `9 i4 S) V7 P* N
2000-10-02 00:12:00    18
! v3 G5 }" `- S  a2000-10-02 00:19:00    21
8 i* Q; T8 K( \) ^0 n2000-10-02 00:26:00    24
" _! b$ f! O% Q3 f" c; \9 XFreq: 7T, dtype: int64
( l" T# A% [+ P- j! r
2 W0 \& W3 s  n- f! Yts.resample('17min').sum()
& @; b$ R# e9 @. [2000-10-01 23:14:00     02 ?% \: @# I2 d  y; \. T
2000-10-01 23:31:00     9
8 Q6 }0 J8 e$ a8 j: B0 K2000-10-01 23:48:00    21
/ G- u+ J5 m6 w# ^& Y, N2000-10-02 00:05:00    54$ D1 F! ?7 n- ?5 N
2000-10-02 00:22:00    241 U, R6 V3 W: h6 b+ z- m- j# C- s1 k
Freq: 17T, dtype: int64
% s4 m% a+ T; y5 N
; Y( D& }7 t' \% f2 Fts.resample('17min', origin='epoch').sum()
& T2 ?1 o2 a0 G2000-10-01 23:18:00     0
& i* `3 n4 c% d$ O2 o+ f! b% I2000-10-01 23:35:00    18
4 n: x! o2 E: h3 l0 l6 O7 U2000-10-01 23:52:00    271 i! X- o# |: X' O
2000-10-02 00:09:00    39, R$ }& z) p! E, ^8 p& F
2000-10-02 00:26:00    24
& }9 a) B+ X, `, X* wFreq: 17T, dtype: int64
; K* W# ?4 ^* F1 J' J1 o# I
5 y8 s; S7 [" R( c6 {: Cts.resample('17min', origin='2000-01-01').sum()4 W( x$ }. `9 D
2000-10-01 23:24:00     3
+ Y9 R. E# D+ }1 q0 j% \! r2000-10-01 23:41:00    156 r# V2 `" t% u. d5 A
2000-10-01 23:58:00    45  ^+ L) H! P. K
2000-10-02 00:15:00    45
; G  _' f- |7 l* eFreq: 17T, dtype: int641 }1 `! m3 }3 p  l+ ^* z

* t9 L# a* K1 x) J1
" k0 g" u- ]% Z" [. ^7 i2
5 o& d) J6 N1 i3 e+ @3
: }/ C7 L$ k, C9 y4
. e1 p, d) |  q8 a! y: E3 {2 M% f* v54 _" A4 m% X' }6 Z- b
61 X2 L* X: l. Z2 V. d. b
7
+ Y) I  \& o7 Z( t. l/ E' q; `80 }: p/ Y3 U) C1 q
9
7 M( o8 E" M) T( z& g- Y+ F101 F6 Y3 v5 |; p
11* D" T) @- @3 l1 y6 _9 h. h" N
12
8 l& n1 W6 L" ?$ T; |) u135 |+ ~, B( n6 y( P
14
* P' N9 t' n+ q( y2 {15
8 H2 A1 C, O5 K  O5 |' e5 ~# g169 h* {9 w/ T% N) a7 q) P* p
17- o; z1 u: o* t; s6 w3 k
18
( r* l2 o$ d- H198 }# S% l$ ~2 q7 u& E$ v( \0 s4 h
202 i0 S- u5 |' R- a
21: E/ E, i- Z5 @5 a3 d6 L# o
22! n( J6 v* ]+ z7 G) z
234 j( q- C0 O; `( a# q- {" D
24
; M, f# N, n, B+ B25
5 f, d; k- A1 D% k3 M263 ]; s7 x& C! i1 v
27
- e* F8 b! {" I' R( R) d3 D28/ J4 [$ l8 J. G) N
29& N) G& Q/ `) O4 y, H* N
30# f4 W+ [6 Z% v' X  }4 U
316 v4 S& Q, w. d0 y7 D, p: ~( J4 C
32
  j" v% c  Y: X4 n- Q2 t. B& T33
0 d: A/ y0 m; O# ~6 ~7 B9 p# x34
; ~; |- y& \# ]35
+ r! x5 Y$ s8 p% S' Q! t364 s8 j8 i$ }; p" ?% c
370 v& u$ t8 n" A$ {
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
. ~, K4 M9 t: i8 y8 m" M: Pts.resample('17min', origin='start').sum()7 h! {$ w$ L3 S
ts.resample('17min', offset='23h30min').sum()
0 K, D& w+ J& v6 w+ s2000-10-01 23:30:00     9
" f8 c: W3 A, J& |3 _" b7 j3 K2000-10-01 23:47:00    21
9 H  t- [) p( {) x9 i2000-10-02 00:04:00    540 N, O; F0 e5 f( j3 w- I
2000-10-02 00:21:00    24
& e) O+ |: M4 \5 \0 FFreq: 17T, dtype: int64
/ B9 j3 z# y! f1
) y/ G* B1 J1 B9 n  @; k0 J% X2
# {7 W4 J/ H5 Z, n1 m( v3
& H) |' z; N3 o, G6 \" w5 o4; H  w0 o( k9 g/ H2 @3 O* v
5
6 x2 V" [/ v5 ?5 u" w: k# B/ u6
5 f8 e% N& ~, V, g$ z! i79 b5 E* A" s# @/ H5 e- w
10.6 练习
$ S, N/ g# ^) QEx1:太阳辐射数据集1 H8 O4 _0 w& ?4 X9 n
现有一份关于太阳辐射的数据集:
& e" c6 c6 c: o7 A# j, ?+ v, T! m
4 P9 w: S) B- C, U* G( C! j! i. P( qdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature']); @, r% u0 ^- ^6 j: B
df.head(3)
+ W& U3 c  K9 t8 z
! @5 E, }& M1 _1 {2 Q- z: gOut[129]:
4 `8 u5 D/ g9 a0 k                    Data      Time  Radiation  Temperature1 x  A# ^  f1 `  E  h; f. F
0  9/29/2016 12:00:00 AM  23:55:26       1.21           48# l: D( z" d& g* O9 y
1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
4 |: }1 S5 W8 t: J  X1 ~2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
) J2 b3 r" k6 y, ?$ ]2 T, a' ]1
$ V# F6 p8 F8 w" R% ]2 l) ~. S2
3 I: ^# g2 o  f' K3# s' R2 T- W6 X1 r
4
( @0 y) r/ [. |6 S; J5/ n, w& f7 C0 F! P) [7 |. E
6& {9 A0 u" H# T7 ~: w" T
73 o; C% o" `6 P& T0 \' B/ s
8' v6 P+ {6 N$ ?; H2 U
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。; y4 x- F" C* Q: T4 M0 b1 x
每条记录时间的间隔显然并不一致,请解决如下问题:& k: r5 B- M4 B. ]' a9 n
找出间隔时间的前三个最大值所对应的三组时间戳。
- h1 J6 n. a# `; Y* ]: P是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
- \; q2 k5 M+ N4 j6 `求如下指标对应的Series:
, p: h/ m$ ^5 r) D9 S温度与辐射量的6小时滑动相关系数
3 U. {+ x, m$ N+ D( t以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列( `8 p  Y: T7 v4 g6 o* I
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
% ?% v' b$ s0 [6 Qimport numpy as np; I' q# f. y/ ]4 J/ @) `! p' T1 {
import pandas as pd7 l! N5 e" Q9 M  T. f
1
) _/ M5 d# w+ z, [2
- {5 f& q: F6 p. }% Q& P1 e将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。/ L1 ^2 D8 F5 z" S8 R
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列3 @+ g. O3 t, o# c5 \# L& R
times=pd.to_timedelta(df.Time)8 l, v6 i+ Z5 ]" n4 w6 T
df.Data=data+times& W. e# c" p; F! r; u; @* e
del df['Time']
) e- Z) b7 a5 f3 S* M1 w6 cdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
+ S2 \5 G3 y! |* t, I6 x  }df
3 W# B2 ~, o. d7 O& p                                        Radiation        Temperature4 h8 @" D1 r. n/ E+ o7 Y
Data               
. J) Y" Q+ Q  Z  A, q8 N2016-09-01 00:00:08                2.58                510 V* D! j3 Z) p* a2 W% m
2016-09-01 00:05:10                2.83                51
- w- `6 }' b/ v3 T8 s$ u8 G2016-09-01 00:20:06                2.16                511 B2 P, O4 }" i6 E# c
2016-09-01 00:25:05                2.21                51
! F  v) t. _/ E" P) f2 \$ H: C2016-09-01 00:30:09                2.25                51
8 Y$ a) T/ u; |  q8 D...        ...        ...1 E( N2 c9 I8 K, o" ^# W
2016-12-31 23:35:02                1.22                41
+ k. n. P( ?- r" J/ Q2016-12-31 23:40:01                1.21                41
6 s0 u. L' d3 ^5 q7 }3 L9 f7 q- N2016-12-31 23:45:04                1.21                42
8 u/ l, B) M: m' @  |- P- m- M2016-12-31 23:50:03                1.19                412 e( ^* U9 i' e( z) O
2016-12-31 23:55:01                1.21                41
; a% n% G; `) M8 x& i
% O% r7 Z+ z7 f: d# ~2 `$ s1
' x: T. ]* a# m! [& \& r5 I0 l2
  x- ?9 i) t% h% ~, F% H* N3; @% E% p4 u8 ?/ x. o; r% s! q' S
49 t: B' K  u% U" a7 s
59 B2 r, j' u4 L- ~
6
0 o0 k' O% c# N) Y. E, g! r/ p1 T" ]7: B! ~# O* N  L) C6 \  Q
8
! N' f5 {8 I: c' h! i9& |" X6 H5 n- q9 N, N! ~: y. T0 g
10. n% O, r. [* }& b" i
11- c+ P5 G3 X3 h/ o9 Z% E
12) m5 {  Y/ I, n& `# L) j4 @
13
) k+ t& r3 z+ j" q1 C, f& Y7 l14
$ B" c5 ^# r  S- B1 H; v! D- L' N4 T15
" y1 `: n2 t' w162 G' ~2 |2 p" T2 r4 Q3 ?
17
  \' D9 w( N4 @) v18/ D! U, {% \* _7 Y! G8 R4 {
19$ h' F5 Z, I; S; |6 [& W
每条记录时间的间隔显然并不一致,请解决如下问题:
; l! ~/ A+ s2 G( G0 m' T找出间隔时间的前三个最大值所对应的三组时间戳。
6 o: c% j0 d, \- k# 第一次做错了,不是找三组时间戳4 I2 B4 p9 h4 C
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
3 T8 z! \- o! pdf.reset_index().Data[idxmax3,idxmax3-1]
6 D( P* P! ^+ u6 |- G% F, k4 i9 ?( m( q6 F
25923   2016-12-08 11:10:422 A: n0 ^- o2 B  L- _7 H1 F
24522   2016-12-01 00:00:02
4 p3 M' g& l3 x: k: ~6 p( B7417    2016-10-01 00:00:19
: T, ~$ K2 D8 H/ p6 T  n1 iName: Data, dtype: datetime64[ns]8 b( p: k. U: ?# k& q' H3 f8 q
1
1 O2 V. ?3 N$ P/ ]" l0 Q* k2
0 h/ f  v# C/ z3
/ E1 O* m$ l7 ?# {4+ ]1 B, m8 g) t' Q% w9 c
5" G9 w1 A" P( M  v# @( [
6
' t5 E, F. N. C9 R- B" |72 N/ B4 k/ I4 z) m$ B* b
8
) {: X0 A! L1 V; c7 o( iidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]; b" y: h3 h% R! }, f
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
7 C/ h: U# M7 A9 T
% _9 U. K6 L6 _; {. h& E[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
0 I. l. m: `, H) Z6 c (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
7 J2 E% l! E# Z+ s6 ?% } (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
( l$ R1 i/ z' |1
2 L3 S: t8 J$ A& v$ T9 u21 N; T. }8 Z# I% T5 ^9 L9 E
38 k( F( F# r9 y! V! L. y( _
4
* ~, }* Z7 H7 k) R# V4 ~7 F% }4 @5- r2 k$ N4 v. g
6* \+ o, p# }# e6 O+ a. T
参考答案:
" x) x2 J9 C7 ~3 R8 @+ b
5 K. K' u' m) X# U, A! as = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()( ~, a: p$ T8 U: j0 b
max_3 = s.nlargest(3).index
( Z7 q. I7 s2 R9 y  Zdf.index[max_3.union(max_3-1)]
0 j; L  f1 [% ^2 d$ K3 G+ D: y5 f" b
Out[215]:
8 }5 Z$ q, O* B; X( vDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
9 A/ z, x9 N2 d* A' Z: ~               '2016-11-29 19:05:02', '2016-12-01 00:00:02',
4 a' |" e6 V7 W3 w# \& [               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],6 B# c+ m4 ?8 w
              dtype='datetime64[ns]', name='Datetime', freq=None)* s7 J5 x7 J' x# E
1" a+ ]0 A% n% c; |
2) a. G3 ]5 a( A. C- o! O6 _8 X
3
& i& U6 \, Q# `4
6 o9 T0 U( A& u' A2 H5 G( T7 f54 U; ]6 C9 |* g0 g
6
. b* s5 f0 W- u! w7
. J6 n. [# k3 j9 p% I' Y. d3 K8
! ^: y1 v. x  U3 h9
# S& A9 I3 C9 L是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
# I& L0 u" S; k( i, \# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
( J9 P! a0 W1 P+ j4 _+ Bs=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
3 a3 J6 c0 {3 e8 [% Rs.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
2 A( x$ H; j! ^9 F6 K0 u( f
! ^' u& q- r) \: }: C* K(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)& \* {* u! Z+ P
1
7 t6 V& _% B4 s; i4 o  ]/ w22 r8 o+ ]6 [4 D9 |
3! e( c% p. M. i& U1 R" S2 `
4% J' {0 @! C2 v0 {* u
5
) a' L$ j9 K/ y9 F%pylab inline9 \; v8 {5 z8 |: t3 Q3 D* a7 m
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)8 V+ T+ u9 T) \, u
plt.xlabel(' Timedelta')
  w- B1 n# V2 H' z- m: M5 d! u3 eplt.title(" Timedelta of solar")
7 ~9 ~# I! D. H1
& o% d# [+ ^% E7 u  g2
* ~/ `" c- T' H# {3# A5 C/ C- @+ b& a
4% B4 z+ O$ G' l4 ^9 W3 D1 h: h+ _

( h8 S, O' F& \; D* N/ ]0 d5 ~) w
: L( ~; Z. i1 x) b9 O求如下指标对应的Series:
9 n! k! v2 P) [+ s" I  \- {温度与辐射量的6小时滑动相关系数
( H+ n4 k1 W& m* B以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
2 z& F7 o. w5 l  V, ?4 t' M) [4 a每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
9 [; W6 A# B3 Y/ Y: ]" V5 K+ Gdf.Radiation.rolling('6H').corr(df.Temperature).tail()
/ O, f  m% \2 M3 e2 e9 \: N" v+ S% t$ k
Data
  [, E# j6 _; {# r6 C3 J( W2016-12-31 23:35:02    0.4161872 E. Y# [+ c0 I" J7 Y5 r
2016-12-31 23:40:01    0.416565+ m& ]7 F; C! T7 g/ A
2016-12-31 23:45:04    0.328574
& j5 e  R: R2 j8 e' w2016-12-31 23:50:03    0.261883
/ ]# K% {; J1 |1 b: X! i2016-12-31 23:55:01    0.262406
7 `+ B6 C6 _3 e) Sdtype: float64
, j/ n1 c# x8 D5 j3 J1& @; F/ V& L" G' _
2+ D! h" J+ g& c  V; U; N) V
3  J7 a! y4 O+ n
4- p' `8 l( ~! x4 J4 t$ K3 o& e
5
2 i3 l! r. ]% R6
; z( m8 g; G7 g0 `6 v1 B7
" I0 X" _* ^7 ?/ {; _3 N8, E# g* I; t# ~4 @, A! J' o& t
9
, a# y8 L+ S4 _. f' j% D5 _) D7 Ddf['Temperature'].resample('6H',offset='3H').mean().head()) u5 Q: L7 z' K, W! O& A
+ p* E4 R/ w8 m9 g& g
Data% m) ?: t" `" f2 q4 s3 S+ t
2016-08-31 21:00:00    51.218750  }( g8 O  ^' ^" |
2016-09-01 03:00:00    50.033333
4 Q! |. K2 G5 P/ L2016-09-01 09:00:00    59.379310
1 o' f. s% O: Y2 Z% {/ N2016-09-01 15:00:00    57.984375, s0 D" ?& w+ I( m1 q) ]; ?( L
2016-09-01 21:00:00    51.393939* V1 }* l( a* @* p# T
Freq: 6H, Name: Temperature, dtype: float64
; I' q! I0 F1 \! b4 _( S1! s% L# O/ _. k+ {5 [
2
, f. o- I) i; ]* s: _9 {7 B34 d9 [) i. M# ]/ Y& q
4+ [* u( F7 S' g
5! i4 _0 V  r( s
6
4 l. k$ T0 H/ k& D7; s7 n- l5 v: s( A% G/ X
8
& L5 E5 F$ w) f9
2 q5 [1 b2 `# b; g' |9 z5 Y最后一题参考答案:. w+ Q( }) c) r( s- _, m
. B) A+ Q2 s" H: X$ l. \2 G! F
# 非常慢+ s3 k  D& P3 `4 X" x
my_dt = df.index.shift(freq='-6H')
6 m. f% U2 |; X  _1 gint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
/ l/ m+ I6 t7 r! l/ E8 D4 wint_loc = np.array(int_loc).reshape(-1): H7 Z4 ]8 p5 W+ `; V; F# Z
res = df.Radiation.iloc[int_loc]' e9 g. h3 j. W
res.index = df.index% Y0 I* B0 P" J: `1 f7 c* s
res.tail(3)
6 `: T. s( M& g2 ]) ~- o" `1
8 q5 k, U' }# J4 ~$ V3 S20 O3 H+ `2 s. W  Z3 ~% c
34 |' [# L: N& `$ z' ~
4
: _) f9 v. H- ^2 E53 o8 }( [2 a; L7 N, |# ]' [
6; c- v3 V7 Y0 p: }" K8 X+ P
7
5 [; F9 t9 J1 c! b# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
, |% `, ~% Y2 D; Y6 @; Z" btarget = pd.DataFrame(
' Q+ U! I7 z: G+ f    {
4 y: N! j, n1 B! I; x        "Time": df.index.shift(freq='-6H'),
$ ~  o( u% q: f0 f        "Datetime": df.index,
5 b1 n3 q( A0 k8 d2 r" Q8 l# e    }! I; u8 s* G, D8 j% d
)
: ?* |. X5 c6 T( Q
% Q, n- A" O2 K$ D  J/ x  rres = pd.merge_asof(4 E+ Y* {! m) G. n0 w
    target,8 f9 w8 ?2 G: S+ u
    df.reset_index().rename(columns={"Datetime": "Time"}),; \, o% U9 t! F9 p. v% C9 C
    left_on="Time",6 t1 _% A+ P# V* R' o/ D
    right_on="Time",; B+ D: h1 L! B" j% h
    direction="nearest"
. d- {9 f# z7 e5 g).set_index("Datetime").Radiation/ g+ ]8 k* A& R* U" U9 J% d0 W
9 J' G! E; R- M4 M* X& l
res.tail(3)
% u) k7 O9 q3 D0 U* VOut[224]: ; p* z+ D( y4 T. T- C8 I$ K7 f4 M
Datetime
4 R! b; R2 @1 ~4 _6 U, b2016-12-31 23:45:04    9.339 e; Y8 ?( G# V; ?: G
2016-12-31 23:50:03    8.49
0 r& }9 B+ I! m# H- L2016-12-31 23:55:01    5.84
  r9 o" K9 W; @: S2 Q: n( b% o* q6 NName: Radiation, dtype: float64% C/ u" q- \, ?
4 D; m$ k7 I4 }4 L4 P' A
1
3 S2 x: j3 X' S2 N8 e7 e2$ {* C7 [% Q  c* z  q+ o/ @- Q
3
& h" Y. b* k! `% c4+ ^7 `* o- b7 ~) J
5& |5 h, Y3 F2 d' a" J
6
* y- J0 y  W7 S) W7' L  [5 H. V1 ?7 `% L. W/ r. U1 t. O
80 Y! L% U' Q5 C4 K+ }8 e* e/ j2 f
9
' `6 r% `* ]6 o101 \$ d! x' G8 R# O9 S0 t9 D% o- L
11
% O( q7 }2 @; H; u6 p, Q12
( S3 m1 H' D  a! n! U13& f2 Y5 ?  h) X, E3 r; t
14
" q7 V$ X; x3 @7 T4 c4 g* l  E15/ A5 z- u- ~+ \! d3 T+ d, y
16
1 ~+ j3 D8 S# H% d' Y- c17
( A  z3 A5 _  h" w$ P- ?18
5 g: b* T/ |! t+ W# y8 u. q19
0 f  H  e: G  c20
0 i; T7 }- D. P, Y) K: }% m21( g5 v8 D; ?! g
22
8 X* N2 S2 n6 H! ~  y" d23/ s0 d: A  V5 g" T
Ex2:水果销量数据集
" }' j8 j, @6 B% @现有一份2019年每日水果销量记录表:
% P' Z; o- W4 s+ O9 X
8 c; i- F) S8 v; ]+ j6 {& i; Jdf = pd.read_csv('../data/fruit.csv'); A9 s9 S9 J' W5 ]# b
df.head(3)
& w' \8 M, b0 W+ i5 k/ k  H1 }1 U1 O* p: S6 K, ]" `( ?( s
Out[131]: : q6 n4 k& A; x4 c1 J. I
         Date  Fruit  Sale+ n; D3 |# U! T7 p
0  2019-04-18  Peach    15
. G+ @" h# C0 ]* G$ l, n1  2019-12-29  Peach    15
- X! ?: ?# P! ~2  2019-06-05  Peach    19
, n, F* V& F. H4 M" q1
% L& V$ T% S5 l2
" G8 R1 a5 ~9 S8 F. C- R$ I3
0 L0 t" R6 I6 h3 h, v! A4
( P- J. P9 |5 W7 Z. v( _5+ f. ]* Q! L8 g
6; V5 x2 H8 T/ j0 M2 o. T2 e9 e
7
: J# n; F: b; h7 A9 C% x8
) Y) p  z+ f+ F3 S9 p3 z统计如下指标:
, l; _4 X- P7 v每月上半月(15号及之前)与下半月葡萄销量的比值
; ^' R# V: q/ d3 m" M" b每月最后一天的生梨销量总和
9 ]5 L; v' X0 p! ^6 G5 a8 E9 a- a. B5 H每月最后一天工作日的生梨销量总和
6 Y$ j* Q8 L5 \5 w9 @" y7 B每月最后五天的苹果销量均值! w7 _# ?8 @! G
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。; e! D: x; V0 [7 U
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。  E) N! `* F+ f. Q! {9 \
import numpy as np
/ E) ]6 u+ |4 u6 I/ G/ Iimport pandas as pd& Y* f" R0 R' o# z6 N4 s
1$ g% o* ?  o5 K5 Z) l: Q% {& h
2
5 q" t6 `4 O2 c; [+ Y统计如下指标:
9 A- s6 [4 {8 z8 |# ^4 j: z! v- r每月上半月(15号及之前)与下半月葡萄销量的比值5 g/ u! ?* }* R3 Q- j3 ^1 P
每月最后一天的生梨销量总和
; ^- a8 h, o3 ]' B每月最后一天工作日的生梨销量总和# X' o7 {1 R; G
每月最后五天的苹果销量均值
/ h. O' _& Z* I7 Y) G: h# 每月上半月(15号及之前)与下半月葡萄销量的比值( e( Z/ Z7 l; A3 {5 E- B9 E/ V
df.Date=pd.to_datetime(df.Date)0 f0 z- N8 v4 p
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()$ ?  M" h* r* E7 I
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊  B4 e* G5 c3 Z0 F
sale=pd.DataFrame(sale)
* _! T9 S" R& Y' w( i4 H* i* Asale=sale.unstack(1).rename_axis(index={'Date':'Month'},3 U( T/ I4 h8 E6 n
                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
2 ]* L! f' V0 b) ?$ Hsale.head() # 每个月上下半月的销量$ K; n& o" Q# t! ]# k. T
; S' Q/ x$ g  Y* y+ S. v) L) V
  Month        15Days        Sale% w9 ?* Q! h0 G3 K, e( y0 z
0        1        False        10503
: Q- q" x) S2 R$ C8 e7 U6 u9 q1 U3 ~1        1        True        123413 K# v9 u4 R# h: A
2        2        False        10001
0 Z' P: ]+ ], B/ h3        2        True        10106
# w* U% q7 ~' D  N$ S- Y4        3        False        12814% r7 y9 a6 j6 M/ r' F" B4 a7 P
! s( z8 C  r4 k$ A+ j
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
* o4 G3 Z: h. I. B6 X( z# x" g6 Esale.groupby(sale['Month'])['Sale'].agg(
. ?% e; G  w. b                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
7 F0 F& _" e3 p1 l+ a( l9 `, e4 M- W. ~: S" [: N1 T
Month3 z3 i% Y4 w- h" |$ a, P: ^
1     1.174998  z5 C( P) H( E# Z  T- {
2     1.0104993 [" }, u5 @8 ~: E
3     0.776338
+ h9 P- ?8 Y; R# J# n4     1.026345
2 r% ]( z, F! U6 G0 x5     0.900534
/ C# w# I- Q  C. I* C6 n* U6     0.9801360 D" {; e1 ~  h- m
7     1.350960* l8 I8 I+ _& f& L
8     1.091584
' r7 e" t$ W: T& ~' [9     1.1165086 Z% v% O6 }% J/ H& c/ Q
10    1.020784
, c# H. q+ Q0 V3 G, ~6 p11    1.275911/ D5 Y' U3 m4 Q3 o) i( d  E
12    0.989662: ]7 o7 U: ?3 u$ H
Name: Sale, dtype: float642 V! q6 u1 f) V# M, Q
4 a6 V( c' Z$ L9 O, M* i
15 d& |( F* \: N% j7 l' J" V3 K" L
2
4 J$ b$ ?# P" `/ y3 K. [9 G; S3, X' e4 O  W# z% R
4
& W3 t9 y3 C" l5
2 f% _+ k" @2 @6
2 z! N$ o8 N9 s$ z9 l# ]' e7  X" N2 I7 W7 I9 }
86 l( z) z( X" U: _1 X( H$ X, k
9$ r0 _/ O! p2 z& d# \' j
10& D2 Y6 ]6 H# m! c9 L  ~- p
11. x! R+ q0 e5 ]$ v5 Q
12& S" G) H  o9 H: z# ]
13! S2 w& I) S, z3 s% t& C
14
/ o, v  R" X6 K" P15( N6 y8 W$ L4 D, S) g$ c7 x1 G  q
16
1 C- P0 g4 c! p" D' t4 B175 A8 K0 |7 }& F! M8 Y
18* R% F$ @$ j" x8 M; B- y" E
19
% z$ n3 T8 u+ h$ }, c208 {# O/ I$ F$ O; d
21
/ M7 |6 ?6 e2 R- b- H" q7 F221 K9 U3 J" R- T9 h$ x
23. j% p5 i3 R8 A) M/ z7 n* B  `' n; m" Q
242 n1 g/ T& O) x6 B7 D
256 t) R7 N; U" @
26: K2 F$ c8 m4 S: C. {
27  f  f! t9 d! }1 U3 T$ N7 C
28
* u% N( C; w* ~/ M0 |) W& w$ U29
+ g) X# Z4 w8 U, X5 l1 r/ W301 t, X: R$ C- ^2 t9 r2 `8 C* L
31( R' s  H) g9 L5 i9 }
32$ ]/ B2 {) x+ M2 ?
33
1 _' S1 [8 p; q0 h. C2 L34( e% S+ N& y; M" U3 j
# 每月最后一天的生梨销量总和9 E8 q8 _: @' A
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
' z; `, U- m$ a0 ~3 w
8 F7 ?6 w0 V0 a- Y6 h4 w& eDate
2 }0 r- I* {, q7 Q( }4 C' W2019-01-31    847
1 ?: i/ e% a3 f, T2019-02-28    774. b6 w: ?2 `1 }; b
2019-03-31    7616 V2 L: [, h8 l$ J! ^4 H- d7 h  v
2019-04-30    648  A2 e$ _" H0 x* h; G
2019-05-31    616
4 g$ y! g2 v: P; A# h1, U) i# ^% o( [, F2 l$ B# \- r' ?) _
2
# v# w1 n7 g. D& Z3/ y. l0 O  P7 y% [/ P
41 J6 Z% Q. K$ y# \  l' q
5
# L& Q; a0 |& `3 |& y& d( T( H6
1 {  M& N8 O5 q* b. H7' F5 A  u5 I5 w  J7 R% P
8
4 o- t/ `% L& D7 t. t/ _' x6 d9
$ S, n5 l: {- |; T/ t4 Y; W6 R# 每月最后一天工作日的生梨销量总和
$ z; _3 C: e2 I- Jls=df.Date+pd.offsets.BMonthEnd()
& a5 Z; M5 j) L& V8 Rmy_filter=pd.to_datetime(ls.unique())/ e$ i+ G& }" y- m0 o/ h' P$ T
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()' ]- M; {% U/ F7 M/ y% f

" J* h# x4 E. v  t$ j. ^  E- fDate
2 n9 s+ `" {: P3 C4 D& H2019-01-31     847
3 V- o( E. L/ x' N2019-02-28     774
- a/ R, ^$ V% {" m$ E) N2019-03-29     510
' q2 ]: q/ `5 X0 Z; f6 c; x. a+ Q1 x7 e2019-04-30     6484 G8 ]: h/ o3 X0 ~
2019-05-31     616* c0 u' H. Y3 C0 s5 m2 b2 w6 k
1
: M8 N4 e4 i. `; R7 x2/ V% g* ?4 m: r! q0 E, ^& K
3, f4 ?( c! s& O, v
4
8 O5 K! ?5 m6 i% t4 M0 M( [5
4 M5 H5 U* u( h+ }6 y0 D& Y6# b, j' Z8 p4 v, L
72 ^- G7 G% p. A8 A
88 G  f; W' I' ]4 H  p
9
% u1 e# S) s9 }$ ]7 W' E2 j101 e. [- a% f7 m0 W
11
# w* x8 o* Q( X! E# 每月最后五天的苹果销量均值8 ^: b9 M2 O7 }& g! v" f
start, end = '2019-01-01', '2019-12-31'
. ?+ |5 m9 f" |, H' Z" G# Mend = pd.date_range(start, end, freq='M')
0 K1 q" X! }- K; s2 Rend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差, ~+ X, K7 d# C! \. r
9 M6 U  P/ M( g- m7 S: O
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),). Q; ?& X) u) i' V
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天; J  N9 s, J7 F! F+ c$ W  @# ?% r5 B
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表  S( Z6 C1 b- f# b" Q/ V$ f8 k7 M1 D
7 g6 [4 L- R% g) i' ?
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
# p+ O8 Z3 o+ g, _% T" T/ {9 Lapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()9 j) D8 s& q8 r5 i. m

& r6 E, f3 q# E4 H: {, BDate) N( y$ G; `$ p9 F8 G) f6 {, k
1     65.3137250 \2 R+ c% e, X+ n, m- d7 f
2     54.0615386 L2 V- o6 D" u0 A" b' v9 G
3     59.325581
& i2 j- j) E2 o! |* K4     65.795455
) n) J) o; @4 I! M9 B1 N/ H5     57.4651168 s3 }; `+ p+ l9 _- S
3 e) U6 w. y3 `. y7 g. r
10 E. m) n  r2 i6 E' o8 [
2# W- U& W( j9 c
3
5 |5 r7 C4 _) Z" l) V- l4- v7 n- Q% m  y7 _) ^" r* `
5
9 p1 m* W9 J( \/ G+ v+ {6
2 `& l9 t& e! z+ o7
: |$ y* P2 Q8 z8
  F; k9 S1 d; l' q3 s9
9 i8 q2 t6 y+ j10
- S& T+ T8 ~" ?4 X- Z117 N5 `# p% M0 a% h+ h3 |  E4 ~! ~
12( ], K; B0 X* M
13, |" O: X- q! Z# d, k8 l
14
2 R% Y+ T, ~1 l1 I15
$ b8 j2 s* O# i8 `. ?16
# ]# Z) s9 q$ p$ V$ i17
& n1 s, Z5 C# ^! K) u4 o* ?18
9 z( ?9 [6 Y9 A- s' z& x# 参考答案:. e# Z0 [( X) N9 J+ N0 t9 X; U
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
+ T4 g6 y* O; M, B            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)  Z$ ]' _- F  z/ {( C7 k- z$ f5 T

$ ~) T3 v5 x" h- s" y( gres = df.set_index('Date').loc[target_dt].reset_index(* `$ s8 @: t. N3 q6 U
            ).query("Fruit == 'Apple'")3 S3 S7 z( r. O" }7 S
' X/ a- ?: B' c( B6 Z7 D9 Z
res = res.groupby(res.Date.dt.month)['Sale'].mean() z! P9 k/ C  I" D' n  p
            ).rename_axis('Month')
) u) E( W( o3 B/ `5 s, R4 g
8 {1 T; G0 z, V5 r3 @" o9 K- r* E$ C1 ^% j
res.head(): ]2 M( Z  y1 n; S8 |2 U
Out[236]: % A) n9 ?6 R4 @- {
Month
" U6 \# |/ Z' ?# E) `& L/ A+ o1    65.3137257 g8 D  T, q+ K% l" m4 i
2    54.061538
1 x- m4 `; {  J& R3    59.325581
# A- S) U- G1 M3 S0 e4    65.795455* T5 n  m) [) {: _
5    57.465116% X  h8 j* P! e: ]/ d  j8 O
Name: Sale, dtype: float64
6 G+ Z6 c# _: o, K2 ^
; Y) K* X9 G6 C" D7 U. b2 r1
2 z  T- u9 |6 E$ [' c2$ S4 r  |6 W' I* P6 d7 w
3
3 Y2 B8 k* _7 H' ?% A4
6 _; v( \# ^" A3 g, K9 ]( c5
! o1 T- E4 M1 ^& e* V. H+ G62 c2 b& d3 p( d$ ]$ F0 x
7- N2 n( E: ]: P, {
8' {' ]8 J. T+ k$ q
9
( o* G; D; ]$ [) f% c+ y. W: u10% J5 W. L! u/ _1 F3 I' [
11! R7 f' |- m9 v% n0 N
12
% Q, S7 ?: |  G4 C2 B1 V$ V13
7 B1 L, b- `% o14& x  ^. q! D8 X) @9 E
15
- w: g( n/ ?) K) E, p1 s0 O$ N16
7 E3 h& }  V1 h; `) K# L17; t. y3 F; X# C+ D
18- U4 C- P- P7 [9 F6 L
19
" z6 e" Y" R0 }20
3 F4 W! C+ q8 j3 H1 m$ Z" R# K按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。( u/ W4 ^5 y' f- \
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date./ I% q& l' N" P- V* S
                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 2 R9 g0 }4 b- e; T$ `
                                        % X2 x8 R# ]+ y
result=result.unstack(1).rename_axis(index={'Date':'Month'},, T1 Y  a/ I8 x6 D
                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
1 K. p- W/ A5 F/ ^/ Qresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
. Q( J& j: i  Z% {. rresult.head() # 索引名有空再改吧
& A: z+ F6 s4 F! J6 j7 O
: ?7 I4 s. i5 W4 q, ~          Week        0        1        2        3        4        5        6
8 n5 S: m: f% y& pFruit Month                                                       
" x" Y( L/ G: D% P1 l/ a5 |  cApple        1        46        50        50        45        32        42        23. D8 ]& y% h% j
Banana        1        27        29        24        42        36        24        35! `/ u% U! h; f
Grape        1        42        75        53        63        36        57        46" O$ ~+ l9 B. B3 g( p* i) d5 S
Peach        1        67        78        73        88        59        49        72
: c1 v5 f- X4 WPear        1        39        69        51        54        48        36        40
9 q2 @! |2 E2 g) ^  \- r/ p" L1
) Z: _$ g  S' H- B$ @* P2
8 F  N) k3 M+ h0 v  g+ u3
) X" I; K: M' H4
2 b( G2 z) _, E. p3 ^( H- p+ j& h5& ]  d" _, F6 C, h6 ~( n( }! i
65 ^4 E' c9 ?; A/ A3 A$ H
7
9 @- Z# g1 `% u4 S+ Y" ^89 r) v+ ]" e+ ~. M
9
+ K. h1 i( j" u' t9 Q) i100 A$ i+ y& S+ e0 Z6 k! a/ [+ L
11
. t' o6 r% F/ i12
: e  h% I  }8 p13
" ~# U: R. @8 R$ ^  g; h14
. C1 [$ t  V6 F9 J6 b7 |15$ w9 }, }% t. M4 y
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
2 N4 @+ I+ K+ z9 `; f- O# 工作日苹果销量按日期排序: i: K- @4 ^3 I* q0 l
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
7 a7 X8 I. g1 pselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总+ b1 @: n, d: i) {" g5 ]
select_bday.head(). u- a8 q7 }2 e) E5 N9 U

- _! }& \9 O; l# M5 |4 XDate" a$ |* \( G. U" R, ^* d9 I$ N: W$ T% f
2019-01-01    189* z/ p0 \2 P9 I# a
2019-01-02    482
8 N/ r3 R1 P9 F) v  O* }* E2019-01-03    890
! H* M, z& {2 n# Z; k7 B2019-01-04    5501 S) k; F; g8 w6 T1 e
2019-01-07    494. B9 R& Y3 f8 z' M! d2 s* _9 s
$ Y* ?5 B1 _( s5 O0 y
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
. ^$ d& @8 X( l2 sselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
+ @) k1 \) ~9 m/ Z: z# e3 y1 I  n8 B. j  l
Date
/ s# D- A$ L* Q9 W+ F2019-01-01    189.000000- q5 b! N! g* D
2019-01-02    335.500000/ R+ R0 Z( |" A7 ]1 x
2019-01-03    520.333333
" l7 h" w: w4 `" s6 J$ g2019-01-04    527.750000
* `( Y& v& V" G. `2 l2019-01-05    527.750000& Z* l0 v8 v& a- t5 x. _+ M" v1 [
, @) E' z1 P' d( C: f8 o
————————————————% u7 k; Z. n7 `" ^7 \' m  M
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
  R' K8 C% d  C% ]# l: `, d原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
5 M! V* j' a* a6 s2 d' M) i" a
2 c" T; ~5 d# F6 Y9 a, b% q
' a& H$ N4 Q5 {% W




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5