数学建模社区-数学中国

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

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

, y, L4 |8 U2 m3 D* l9 d; N
; I7 q8 q! q7 T) Q8 x: Z+ A0 v0 O# R% B) B( p' x
文章目录4 @4 ^/ B& c" a( s
第八章 文本数据7 t$ M' r, i- ~( \
8.1 str对象/ y6 l  f; h- F5 @2 `
8.1.1 str对象的设计意图1 L6 C( L3 L$ y  W9 b. L$ a
8.1.3 string类型5 G* H. j( t& Q+ x& v7 T2 o
8.2 正则表达式基础+ j0 j( j! w# ^5 D
8.2.1 . 一般字符的匹配: q0 }, A4 j7 Z, }; P
8.2.2 元字符基础9 |9 F5 z9 e' k3 R3 |3 s
8.2.3 简写字符集+ h" w& p) J& L
8.3 文本处理的五类操作4 d. F3 ~7 n3 B# C! o, U8 ]3 B
8.3.1 `str.split `拆分
7 k" O  b* h$ Q8.3.2 `str.join` 或 `str.cat `合并# P8 V9 b5 v4 ^$ S
8.3.3 匹配
% {( R- U  N! {8 {8.3.5 提取6 N# M' I/ v7 l9 L' m4 z! v0 c
8.4、常用字符串函数' I  i# W  j, s% A7 w
8.4.1 字母型函数! u  c, ~( {8 a( r4 `* U
8.4.2 数值型函数
7 ?9 @$ D6 \8 V) u8.4.3 统计型函数
% [! `8 B1 U, c" _2 @5 i6 I9 ^8.4.4 格式型函数. Q8 i8 ?+ f6 }$ \4 I
8.5 练习
9 m- ~0 ^7 q" k/ y1 R9 DEx1:房屋信息数据集, N  G3 y7 G: g& q! Z+ B$ @  P' r
Ex2:《权力的游戏》剧本数据集
, W% c4 Z! d4 _第九章 分类数据4 V& t1 k2 Y" I  ~* q2 o
9.1 cat对象: A1 E0 ?7 _# O2 L5 O
9.1.1 cat对象的属性2 `: f7 }1 X% u& n
9.1.2 类别的增加、删除和修改
; K* G( S. i9 L$ a) N* }0 [2 V9.2 有序分类
7 R5 L" O+ f& |) q9.2.1 序的建立
* \+ r2 b1 \5 q' P1 T9.2.2 排序和比较
' x( p- f3 Q: E9.3 区间类别
3 B( }5 w: N( Z6 T! t  f7 O: k& O9.3.1 利用cut和qcut进行区间构造+ F0 Z2 G" W2 ~9 ~
9.3.2 一般区间的构造: ~9 k1 ]8 O( ^' T% D) F" W: A7 r  W
9.3.3 区间的属性与方法' }2 K2 V( G& O2 Z
9.4 练习/ u: p! j8 ~( }! A; C" c+ H1 m& k- j& x
Ex1: 统计未出现的类别: e. i% o: z; \1 m) H: z' r+ S
Ex2: 钻石数据集: t8 |& \) V: E0 ]; q4 i# ]
第十章 时序数据! g+ {) |$ U, D8 ]  N* ^6 Z
10.1 时序中的基本对象0 A& q. R  ~0 e+ [( s
10.2 时间戳
% p, Y% e- X. [10.2.1 Timestamp的构造与属性) V( O$ S; S1 v; A8 P% n
10.2.2 Datetime序列的生成
4 I; ^! B3 n7 X$ c) ]$ {10.2.3 dt对象$ s9 y' N; J7 }- G! j; Y+ X
10.2.4 时间戳的切片与索引
. Z: L) D5 M2 {$ h10.3 时间差
; T3 b  [  v( y9 e$ d10.3.1 Timedelta的生成4 p' k9 r; a  `# y$ j- Q
10.2.2 Timedelta的运算( H' \. e2 o. u% O3 r) J3 \
10.4 日期偏置
# |" L: }! Z' o1 k10.4.1 Offset对象
, d& V( l" r7 j- y9 K0 g: u10.4.2 偏置字符串* v8 ~, o; K; g4 G' m+ b7 S
10.5、时序中的滑窗与分组
! p! G1 s2 ^' e% Q! h# q! Y+ {10.5.1 滑动窗口
0 r3 M( A. K& n. k2 T7 J8 A10.5.2 重采样4 {% c6 ~  y( `  k+ l2 h" r
10.6 练习1 C% ]& K2 X% C0 @+ t# n8 c
Ex1:太阳辐射数据集$ \. Q5 k& B: H/ {: H  I
Ex2:水果销量数据集' n6 [# S! j; g6 X2 a; k
  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网# ~2 p' `3 |6 f/ g; M
传送门:. l/ ]) J* i7 |/ V: P
# u; h3 M' V% s$ e) z
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)$ Q" I& ~6 p* J
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
7 n; _6 z  @: f0 y+ J第八章 文本数据
5 W% N7 q+ O5 Q2 v- a8.1 str对象& h, G/ G1 s) u7 d7 q+ Y
8.1.1 str对象的设计意图  A# `( @; a7 u  @$ X/ }5 O
  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
' B+ V8 g8 n: K7 e* U& Q
, l* N% \0 @" O$ O3 \8 svar = 'abcd'
( c) ~4 f; d  }3 Tstr.upper(var) # Python内置str模块' ~# N( r  [1 D. q2 x4 m; e9 x
Out[4]: 'ABCD'/ P6 d1 Q! ~! p" Y% H& x

& P: l7 D; h( J4 A# ps = pd.Series(['abcd', 'efg', 'hi'])  f, Z* y8 Z. D0 O( U. g
9 J0 J9 n. a5 ]+ ]) H
s.str- c5 o2 H: X" G& u; I  [$ z
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>5 V  |, n- d" u. Q% ^0 L

7 h7 z' B9 C3 W2 ]) x* L7 ?s.str.upper() # pandas中str对象上的upper方法8 U- S$ C/ I3 z
Out[7]:
- @7 U# a7 a$ j% T, S" Q0    ABCD- n" B7 A0 E0 H5 Q. Z
1     EFG$ {+ L6 V/ d5 V; e! ^
2      HI4 s0 b& G; B7 p" S  d- S& l
dtype: object
! I4 t1 d  V; J" h1
8 R, e2 u( ^6 `( _) J' W" S; W2
' Y$ q6 B" A" i" i* E8 Q: v3
' v  [" L/ T- N49 X$ H) G; ?7 l6 w
51 x, z2 K9 O1 y4 ]! A8 ?
6& M# M& t+ b0 z% T0 w
7
: R) n. n4 i, [! R8 B# ]8
( N( o. x' U. S3 K1 t  \! V) `9
' F, G9 T+ [4 U! U% z10& l. t5 I1 u  c! K
118 ^+ q' l- @" Q
12
7 S3 B0 N* @" b, i& I# m0 L13
1 c7 i9 U+ ?/ F146 _5 V% a/ ]- ^( ^* x; y, g; o
15
6 s5 |: B+ ]* {5 t; {$ ?" J& n2 z0 P# A8.1.2 []索引器6 ~& U3 S' z$ R2 x
  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。6 ?( e; F. p+ }. K5 O* }
  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:5 m3 i. l5 A9 Q" \- W  [8 b
4 y0 l2 A4 \1 N% X! [! A
s.str[0]
: |" h0 U9 f& f5 P. r* T) I2 @. {% d. gOut[10]:
  p- q2 G$ Q4 [. s8 o0    a7 v( i7 A2 U- r: e9 U
1    e. ^" y( ]" E' ~/ S( `
2    h
# t9 X: V, T$ K& Q! ]5 N; G2 Mdtype: object; H9 y( O0 q6 Q: Z$ z/ Y
5 y: ^, b7 a; `( ~( K- n& |
s.str[-1: 0: -2]
, T: L& H/ G5 N# m/ ]1 j; b, WOut[11]: 1 G  q7 f1 K. E1 I  N, H
0    db
$ c! l6 r9 P. O7 ~( F1     g
! P7 l8 [( [9 W2     i
0 M: Z4 ~) w' U! x1 `. j, P6 bdtype: object: w' H, l: B1 i/ J! e& ~1 }, `

( E0 W# w* B; y% Hs.str[2]7 v6 A( q3 ?7 X  H8 r
Out[12]: 4 e4 o4 R% ^: X5 K+ N1 G
0      c
" Z% ]  f( }% N3 F4 z6 a1      g7 ^' C2 n( O* `% S3 y( k0 a
2    NaN
) p% _1 h+ k& ~dtype: object2 }' q2 o0 \9 S
3 o# `& `- H; }7 H+ a" c
1; U5 }* Y8 [$ |1 y; E
2  Q1 c# E' F4 q! o- Z* y
3! p1 E, V" i, _6 O# W* E
4
" i" Y* T9 q  E5
9 W2 e2 Y4 R+ m2 @7 J  @# u) _6
- g, U/ V! H- {6 ?79 ]$ T( [+ X, O8 I& \0 }, N: S: t
8  }/ v7 I" S9 T( H3 w) u
9
- R; ^  @& n4 r$ J: D. m10
9 ?$ W+ I3 T0 \9 I4 a  H/ G4 r2 G  R112 M- a- k% @; s# Z+ l* |& Y
123 g) `5 M2 u& w. b1 y- X! q. ?, r
13
* r9 z9 h- K4 s- @& n1 U/ N4 b8 I0 g148 I# X* t* `" h6 z1 j
15$ g3 s& |; |3 u3 J
160 `8 z" b7 W. D5 C
17/ P* z; }+ B& x9 m, [" @- m
184 W5 [) ^! F: S
19
0 R  t# u5 ^$ B4 R! _( F5 _: j20# E/ i# n' w8 f9 w  q
import numpy as np3 d( r) g# w  D# ?( P$ [, @
import pandas as pd
# i9 C0 B7 N" X' v  Z. j. A( X  M& w: [$ Z% t) i& F
s = pd.Series(['abcd', 'efg', 'hi'])+ T& q* e' j6 ], Y* M! Q
s.str[0]* Z; _- D: u  V, q& Y6 \
1
. Q- a$ H$ o( e% ~2 k. f/ |! z2: U# H8 |3 V" R" D- e& u
3
1 d, l( B! m/ v& d$ Z4+ ?% o- D, n( R9 S1 ^1 b
5) c  r. z. A2 w/ g
0    a& r+ j0 Z. U7 A8 {+ o
1    e! X; N6 ^+ @, F7 t7 d/ Y
2    h$ `1 h( y: |4 r9 u+ h+ i1 D8 Y
dtype: object
5 z2 |0 H# z( {, d3 }! K1, b7 r& W4 Y6 C( g
2
! u0 d7 ]3 d' `0 _8 r$ z3
1 `9 y& L9 A3 B( `; u/ W# c/ j4
% ?/ O# K' f! s( v1 g* y3 j* ^8.1.3 string类型! _5 r2 j3 m# g# y5 ~
  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
( X3 t! c8 V5 B% ~  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
  r. k4 j# P1 p: n* F) C" t9 r" m6 o. y. f6 a! ?9 B8 b5 ^, i
二者对于某些对象的 str 序列化方法不同。
7 ~; I/ |5 ~0 M) q# {& f6 C可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:, t, ~; M% d, Q, n# z& o+ |  Z9 G
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
: {; w, O. X+ z* f9 hs
. ^: t$ L$ C; G( O11 N* _/ a- G4 s
27 X8 R" I3 j5 `5 U* q3 ?
0    {1: 'temp_1', 2: 'temp_2'}
( _3 J8 f! T! v! h+ S8 i1                        [a, b]
% x. N  o7 R8 y" h7 B2                           0.5; g! E/ G6 t& _
3                     my_string! c! x4 n: _9 N; j) ?; m# M3 A' ^
dtype: object
8 T7 Q+ L# C5 r: i+ d' P1
, G# c: |! ]( H+ f8 g7 B20 l& G+ l0 K* w% M5 x
31 |+ V) b0 p. ~  V1 g0 ~
44 g# o4 o9 Q, U3 @; S2 m" `1 H  L5 c, [, ?
5' R5 z& \8 b; L- Q: ?
s.str[1] # 对每个元素取[1]的操作- X+ o8 T4 h) @/ _
1; Q( d3 k6 z0 w
0    temp_1
3 I: |* X( y! z3 I; f! g1         b+ r5 Z+ l$ @1 s* l% i& j
2       NaN+ Q: P1 }3 G, U4 M
3         y
4 E6 p# e) G9 Y+ @! Wdtype: object
  x- E6 F0 g1 n. j0 e# B16 y+ g* O; H4 k0 Y7 K. v
28 W( l9 B- P  O# I
3
3 c! l# @) `1 \  N5 [9 O41 `3 Y$ l7 o6 k4 W" Y/ d
53 v8 u3 {2 L- d
s.astype('string').str[1]
) w- F0 O3 `/ d1 l  A8 x/ c* H  j& y1( v# p, F' k' [, K1 q& \- B
0    1% Z: E: b3 h) D9 s
1    '
% \9 @8 v5 t: |# X3 A( g2    .
+ i+ Z0 E( N5 m1 k% k3    y
$ _9 z$ J& Q# W" W3 N9 P& A  mdtype: string
, V5 s" _% v' E* l1$ o" q$ N, ?# T+ F2 r. D
23 h( K7 t: r  T+ ~2 [
3
: J5 M- W" @, |/ [& [  {4- X" W0 @4 \" }0 F  Y  T
5% @  X* ^$ R( g% w# Q
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:  s' y  Y4 _, Y5 M1 m
! P/ k6 g: W( Z
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
; t& j$ S0 b2 X! @- B0 k! Estring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
3 d* r) u, H; Bstring 类型是 Nullable 类型,但 object 不是
6 g5 D+ u8 f! ~) b7 R; K  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
3 S" I$ [5 [* l( u  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
# j; m/ W7 l5 ms = pd.Series(['a'])
5 f) u) T/ @8 V5 H5 i0 C( p" D: S7 o  J* V3 D
s.str.len()7 ^8 j+ \! f  Q4 a& ^, y
Out[17]: & j+ R  o: D, K# i& @
0    1
% A& w* P& j" A: U1 Sdtype: int648 |8 r6 S$ w/ }$ e0 Q" f5 ]

8 J9 X/ ^9 E1 T0 {( vs.astype('string').str.len()- q8 J0 X: y4 a+ R6 g
Out[18]:
7 s' }* g5 A) n  u) @7 E, Y: f) l- c0    1
1 B$ B7 T$ W# P) k- N! {, gdtype: Int64
% d  [- i. N1 h1 Y
) I( f( C: f/ m# h) zs == 'a'
4 B1 `5 d  j" `2 d2 `Out[19]:
. d' J8 P1 W. j% |3 w$ |; H8 u0    True
  r+ v* I- S, adtype: bool
5 D8 C% y" S' k
# ?4 Z1 ?* ]3 Z" p9 h2 Q0 o$ T4 J9 {s.astype('string') == 'a'( q9 K( q8 Z+ L( k0 h! w$ h/ {2 c
Out[20]:
. q& ~, T* w4 h0 Y& |( t0    True4 @  ~& a' @" W3 r: ?& j% t2 X
dtype: boolean
1 @" U7 r: M6 X, v% K3 ]( D/ Y4 g# P: C6 i5 ~
s = pd.Series(['a', np.nan]) # 带有缺失值: O& K2 Q+ c. s7 o/ A1 ~
. n* m. L( |  N5 K
s.str.len(), H) o, t+ ]4 W# J& Q- Y/ o: p
Out[22]:
% t( t' {& l. z7 [+ B- g  \0    1.0; }9 S5 F* Z& g* A  y! q7 d3 X
1    NaN
$ t7 h6 Z' G# S. Q6 V) Ddtype: float64  J' I  @) `: B& n- @5 t

1 Y; e  }: z7 s# z* e$ ?s.astype('string').str.len()
/ Y8 j5 \' i! F9 FOut[23]: 4 q: ?) _0 Y3 q* C- W3 q$ m, Q  ]
0       13 g1 k2 y% [; X/ h  G* \
1    <NA>
3 @! K8 W( g2 I( W; l8 {dtype: Int640 C0 y7 |/ V) h) i0 I7 s; f' B9 @

8 @7 T( s; U4 \s == 'a'* Y3 J5 W. J: l! q
Out[24]:
- ?: f4 d* [  R2 d8 Q0     True
0 Q0 H) l( F* `0 l/ b% Z; H8 E) m1    False% f( }; j/ @( j5 k0 r2 q' j
dtype: bool, l0 `" B, G7 x0 M8 }% \

% A4 S) Z8 |! J0 p' Vs.astype('string') == 'a'
7 b7 x+ K; ^! }" o9 Z* EOut[25]:
) E. s3 d  j9 u; T$ {3 G4 L  c7 s* ?0    True1 v9 T7 Q* V" B
1    <NA>
. Z" j9 Y+ X( ^- Hdtype: boolean
% w; }) \, G/ ]7 L
- b6 K! Z+ p% K$ ^1' q& R4 T, y! V8 u" ?0 [
2" K, b! \* p- h: J$ R" g
3
7 b% i/ _" F5 N7 {* v% M4; S3 `  |2 R6 T0 R, B# S% `3 I
5) m6 e8 d, C! Z
6
) I( c6 G# Z; v, w7 L7  Z8 C3 _9 X0 @4 S2 n+ K
8
  [7 Y  c, T' i) R& _$ O9, i" O7 H/ v  x* u
10
# [5 L6 T6 g% o6 a11
. R4 N) B& h+ S: C  t# K12
6 Z4 a  F( u" {" d13
3 d" u8 \8 R# [0 j; e143 w: N0 j) M0 S- A7 \3 y  a; T
15% M3 M5 ?1 ]$ U6 B: C3 o
16' `' \1 @0 [" }( Y9 X
17. ^0 v( c" d2 a  R
18! m% h. }; H- g2 S3 {
19
! A6 D6 J/ y8 |8 b20% _2 L& I; u& Z9 e' v: a7 ?8 i
21
- M, M2 f" C& A2 {( I- J6 f22: V- [$ E8 i* E
234 j& w1 `! G+ `1 I% W* O2 g, t5 P
243 q# e8 m, b; B7 Z2 \  f& u' h3 I
25
& f+ q- B  }4 B" t26" U& V' L" W/ ~( v* O# p4 ^3 N
27
0 {+ c1 @1 U9 K5 y4 \2 v& \1 Q9 P7 u8 Z28
2 r# V8 m. s- U5 l& W- M6 i29* Y3 D7 X1 }6 s& b! k
308 O1 X0 r/ M+ N" }0 G
31
: z6 Y- i% M- W2 C" ?32
  \% ~) l, y8 n$ o8 i33! B0 L6 q6 S, g8 F3 J0 s1 L) E
343 B/ A7 d- C2 Q
35
9 @- C/ W% [0 H9 t- T6 ^. ?$ e36, k5 ?% t# E% b
37
$ f6 h" A9 d0 T38
- w: _% |( p' u% F39
% I1 C  [1 d; |  W$ ~1 m8 ?. J40
% `" y' Q7 L# S5 }- I& `41# z) u5 c" t2 M: m* Y
429 E# G; ?) w5 M+ K
43
' `' w0 i9 Z+ g- A$ ~9 B# N44
0 |2 a& M% }/ `, u; B8 z45
8 y* @. _; A% {2 j, L4 G5 G46
+ [) H3 V$ z7 x0 Q47- S! l5 L7 @4 p! o" S
  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :, b! o$ o6 N8 A2 |$ w4 D* b

: ~6 Y' V) J- l1 K* qs = pd.Series([12, 345, 6789])' k  j( y. P* F2 Y
5 t( A2 d# v' P
s.astype('string').str[1]/ ]% ?: v4 l, u
Out[27]: . C+ l. d3 k& Z, X# J( d5 r$ E
0    2
8 `& u. ?, s8 D0 D( i2 l1    46 c# n, r. C% M6 a$ d9 b0 L
2    7; O. R6 I# \  b7 h0 C/ [9 Z- X! @4 L
dtype: string: a. M2 U' u% n. S. g3 l
1
, N* C9 a" B% j7 s2. }$ _# b+ I6 r9 F# n: K% c
39 U- o" R  K( z2 G/ F; B* v2 L
4
8 @. O- T, {+ u/ u- p1 y5
: b, O3 r: U+ N/ @: D# h  P60 v+ \- ^9 w& E# G
7# k# c0 p5 A+ j9 j2 Q) J. c
8% K5 K; h& d0 A
8.2 正则表达式基础( `! y& z+ o- h$ w+ F) E/ W: z- e
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
9 l. Z9 {6 B7 [- ?2 d0 r5 A
% \# f8 a% _5 E# n; r: _8.2.1 . 一般字符的匹配8 Y3 B  C) c6 W, V9 [
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
9 P, b# B3 P* @& F" V
! R" m( _2 `) `8 D. @! oimport re6 @9 v& G; B, j- U# ?9 @

; d8 z3 s8 U( u7 A  Nre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配4 |% D' R! {3 M" }# N
Out[29]: ['Apple', 'Apple']
* r, V: i& ~' |/ A8 {1. ?5 ]) m3 o  G4 S( x  @7 ?4 u
2
  {" o& k- I9 j  g3
! U2 K" n; \# ~48 [5 Q/ g. i$ s( O2 }3 O
8.2.2 元字符基础/ n3 R  o; \9 |; Z
元字符        描述
2 q5 v3 y" [% I7 J.        匹配除换行符以外的任意字符
. M0 U+ ~, g# l' L) w& t# R4 T+ X% V[ ]        字符类,匹配方括号中包含的任意字符
7 [! f. B" K. |[^ ]        否定字符类,匹配方括号中不包含的任意字符
) p0 w# [3 ~! p* @*        匹配前面的子表达式零次或多次' f0 h  h3 f3 D+ w# y# n
+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字/ t/ G! {5 x3 B" @) j! |
?        匹配前面的子表达式零次或一次,非贪婪方式
2 O! {# @* ^4 a; n! X{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
) ]$ [, u6 d4 l  e0 l$ W* n(xyz)        字符组,按照确切的顺序匹配字符xyz
- u$ z; Q( w& `  N& e|        分支结构,匹配符号之前的字符或后面的字符
8 O# e. p9 G4 z9 T1 u\        转义符,它可以还原元字符原来的含义, e' j* P& E* G' _, L
^        匹配行的开始
/ |* W& v" ]( V, B7 ]1 z$        匹配行的结束
! [; S. G, n# Rimport re' m* S$ d% ]/ Q1 V
re.findall(r'.', 'abc')0 d2 X( t' r" N; ~
Out[30]: ['a', 'b', 'c']- w1 P: C1 l: A; M& R: B) L" {, p

# ~: J7 n& J# u  ]) _- nre.findall(r'[ac]', 'abc') # []中有的子串都匹配
# T) t. h; x; Z/ k/ B! \9 nOut[31]: ['a', 'c']: ]' b; O( b2 b" @
7 t; A; F4 s% s! ?
re.findall(r'[^ac]', 'abc')
5 r2 K5 @; }3 p# o( ^  QOut[32]: ['b']
8 U+ U  V& `6 u8 [4 X! y6 [4 a' K/ w% b# v- h9 f' k
re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
. g! Z& ^! `+ y& d9 X* B/ VOut[33]: ['aa', 'aa', 'bb', 'bb']" p- w; z5 c3 D$ r: Q

. |0 Q$ s7 _9 l; t+ cre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串+ v& \& U9 L& O& b6 F. K
Out[34]: ['ca', 'bbc', 'bbc']
! V0 d; q6 Y4 L0 G  n7 `' n7 ~  ~" A: P  y0 v$ x3 e
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。& j- H( S+ O3 F- y+ Y) o6 }
"""
1 E& g5 k' g$ @% J: W! g1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。( V8 t" J- [# N" Y: C( n) A
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
' b, `- w, P. c7 `- o3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
! j6 b1 V! ?& ]1 A3 G- F% P% e但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a6 R- p& ]# J  f+ |/ H! \
"""
9 s6 V- |' @+ y0 G, @, z  w% s  {- n3 C5 _7 ]
re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
2 D: D& F# b* C5 B8 e4 s) lOut[35]: ['a', 'a', 'a', 'a']
( F1 A# W& d, G4 @. q/ L6 B# i" |+ t* F% }5 X0 H
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。% W4 J; h3 z! J6 T
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。9 H7 h$ S+ V( C+ V3 l
re.findall(r'\\', 'aa\a*a')
" ?: W/ g7 E: x# h' [" U[]5 u) J6 D1 D6 m4 w: c3 u; @9 Y

/ {+ e& d5 a) ~5 h( \! W, [re.findall(r'a?.', 'abaacadaae')
% ?* z4 \+ m8 Q( c6 n$ zOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
. y6 t6 r1 `! e8 L, |: I+ {1 r# N0 y1 O& D) P" [: f; N* Q
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表0 L6 _! q2 e* c0 S7 x) M
[('width', '20'), ('height', '10')]2 z; h! b" k' R7 s- }" `6 K+ i

/ V+ d' l) h1 @6 [6 z15 H9 O6 Z9 L/ @7 F8 O& g- l& R
2- ^2 }/ n( [9 W
3
) l0 p0 f8 R* D7 u+ w4
* u2 i' M7 q" D5 l, ^9 V5
; W4 @0 A# z: M66 f; U5 c  s* N% i. [! [* `7 A
7% k- t% R4 F: P/ E2 A$ b: u" }1 I
8
: d% [- D! `  d+ X9 x' L9  E  u# S) p- v" N
10! `& R3 P4 D! w3 ]  f) k4 L. {6 D
114 y/ ^9 E6 c; R  {5 s5 l, `
12; C7 D; T1 L+ i9 E* T
13& y6 b  b6 P6 C7 V* {
14
) D% p  X6 k3 R3 b2 a. y3 T. d15; n  M, E' M; J' v  j
166 V! D/ P* S: P3 _0 d  O$ U
17
# K! t- g9 P. w. \& w' Y# h183 ?) T& ^- P: A  x0 B4 L. n
19
8 Y8 Q" y; N& E8 p( m/ J" _20% K, Q  H+ {* e! j# O6 n, n
21
  ^! w" c- e  _227 O, ~  ?" |2 f$ E6 s' s; N
234 Y+ f  c0 @/ A1 b# E/ U( G
24
* C9 d' }  q' [, Z% h* ^25+ B1 Z/ b  c; B* _& H: ]8 {
26
! [/ i/ j3 `& I27
; c/ q' Q7 @9 c2 V7 {  q7 B0 B28
( j  t8 ^: Q& j. g% r29
7 \# F3 T) v9 k; g- O$ U$ B30# ^2 n  W% }/ e; V6 f
31
/ B! X+ X) G% W; b" b# @32* l6 ~0 j; y$ D
33
4 t( b- h2 d( t8 X$ i  v34- x( j4 A3 m* A: K, U! _
350 a4 \2 n  Y: L" o: E! {
361 F$ c0 N$ R! W1 ^6 C
37
& X2 u: l7 u0 ^8.2.3 简写字符集, \" z7 P% X3 N( \8 C  h
则表达式中还有一类简写字符集,其等价于一组字符的集合:
9 P; g* x4 X) b& D2 g2 w/ V  m3 k" `4 d. m' l; t! C" S! Q
简写        描述. J3 Q) }' X, p0 [
\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
, t. K% Y6 \4 s; Z& P4 V  N\W        匹配非字母和数字的字符: [^\w]: |4 S( z' E$ F# R$ y; M5 |
\d        匹配数字: [0-9]
% t- i4 a2 ]- J, ]$ n\D        匹配非数字: [^\d]
8 m. G* G4 C- k8 |. s/ y% h\s        匹配空格符: [\t\n\f\r\p{Z}]4 X/ p, q" s) H2 @7 i5 r
\S        匹配非空格符: [^\s]
: i) ]$ [6 W: o& h9 m. c6 u' p\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。) E- I! {; @" k( d0 @) q
re.findall(r'.s', 'Apple! This Is an Apple!')) j) T. g% }: N" Z0 ^' `
Out[37]: ['is', 'Is']( |3 b6 y4 F& f2 Q! Q$ S/ ?
, A" F" ]& W# J+ {2 ]
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次1 d9 y' X% w6 g$ ^
Out[38]: ['09', '7w', 'c_', '9q']: R+ r( Z) I7 T! V$ E$ T

: b7 C1 ?% Z* fre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)( D( b% ]1 [' v8 E( ~
Out[39]: ['8?', 'p@']
1 h" @4 j6 {! [6 J3 @+ ]  d" W! u+ f: B
re.findall(r'.\s.', 'Constant dropping wears the stone.')8 Y" L. P7 j' Z0 c6 r. x% R6 |# T
Out[40]: ['t d', 'g w', 's t', 'e s'], a+ L$ }; f5 z8 t( d7 o# i9 e+ s
' }3 R6 F; ]$ U  m: ?) `
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
6 o! K3 r7 L2 Z- a- D           '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
- W( S$ O7 h& n( W/ F" [8 a' b6 G) Y  H
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]- M8 P, D! y* ]8 J2 i: f
7 a' G$ T  }, A5 v3 A1 ~
14 k. s( \/ j" L3 y) g. ^9 I- z
2% m# G4 {0 {8 ]& u0 Y/ _! m
3% C$ \/ T( g8 M  u, O. L
4+ ~* o+ e/ u" h* S9 K; Q
56 u! B0 z( u2 |. O; B" V8 ]* G
6# `+ A& L7 J3 y: \: Y+ \
7
0 U- a, j1 ]: V) C4 g- r) V8
/ D( n( l' H) d! [: p/ b/ {9 \  D9
% z' p6 ]$ b! K2 T1 P101 t8 k% h& x+ I# K
11
- b4 A2 p/ Z# ~1 W3 G5 x3 N7 P123 n" {# j. @( U" ^) c/ O
13( H1 V3 x1 i  ]4 B3 g
14
$ Z' J! r: l8 I1 `15
( Y  Y# p1 b2 r3 Y161 e7 e( @5 K6 k6 i
8.3 文本处理的五类操作
( ^2 a7 J8 d; e% o8.3.1 str.split 拆分2 u5 ?3 q+ V; l! {( H
  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。' w1 Z# D& ?/ G$ Y
3 s& Y; {: J) \* u0 l( k* }
s = pd.Series(['上海市黄浦区方浜中路249号',
/ w2 s: G" }0 N" V1 M& M% i& T$ k            '上海市宝山区密山路5号'])
  O% j/ Q2 C+ Z9 [9 f6 _7 C2 O% T& b$ K" R

& ^! G- R. h) ?/ C) Ds.str.split('[市区路]') # 每条结果为一行,相当于Series
9 ]* @  }2 `8 {& O# S' f5 [2 K. m5 `Out[43]:
. m, S# B4 t, M" O: Y3 r  p0    [上海, 黄浦, 方浜中, 249号]. ^5 C$ T0 B* `( K% N
1       [上海, 宝山, 密山, 5号]
6 I+ \/ C6 `% n: H5 T: Z5 |* Xdtype: object4 @' k% d3 i, N! D

4 i6 M$ ?2 Y' n7 Gs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
1 B' v& L' G7 E( {Out[44]: ) S$ D5 B! [% j
    0   1         2
1 }6 S/ O5 K& @1 i- }+ \0 ^" d0  上海  黄浦  方浜中路249号- k2 q% V& o7 f$ M+ y
1  上海  宝山     密山路5号1 e8 H, d( g& C' M3 d9 v' q
1
. j" j! `" }' l' n2
( K  x& Q; _% m+ r" ?31 ^' J" a9 x9 w+ C. H$ O
4" D" a4 ^% N3 p( X. N1 _9 ^3 S9 j
5
/ Y3 L5 p0 [# d; j8 c# k. R60 h  T  b( w8 J7 x
70 Y1 o/ ?! t3 w$ N2 @
8  L0 y# I. a, r3 A8 K* e3 x9 k
9
& y8 x/ I2 p% O; M10; N( w# ?4 m* G) q4 q
11
& I2 O) F3 _4 U  `2 M12- H2 c/ X7 P; h# {( q/ H
13
( [7 W' v* `6 {' X/ Z: u; Y9 k* b' J6 P14
' ]; M3 Z2 `% ]* c4 c5 r2 P4 l3 L! i15( f( n0 u4 T; \% H" A7 S; O0 @( l: j
  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:0 C% m9 Z9 m6 S3 ]  }

6 h5 ~3 X# s7 T; Ms.str.rsplit('[市区路]', n=2, expand=True)
* ?/ |' F5 \0 pOut[45]: 1 f+ \. m3 |2 f4 _$ O' A
                09 g# l! Y4 f% K/ L$ n
0  上海市黄浦区方浜中路249号
# F8 W  A: b8 x1     上海市宝山区密山路5号: E7 u' r( C' P. S6 d2 F. g5 p, e
1) Q3 V( Y# w2 r: H& \4 L
2
3 f4 Y0 s; X/ F' L9 I. }" P3
# h) D( S6 F9 y* G( R4
# V  e* w/ w1 \; X5
* x3 `$ Q4 V1 r" B% s  b8.3.2 str.join 或 str.cat 合并( e1 V" A$ l2 y1 F- u9 d5 c
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。  C, O" I: ?" s; \5 X7 J
str.cat 用于合并两个序列,主要参数为:
' p& I7 V1 n, _: D$ v, ssep:连接符、1 k/ `- m( ^+ \2 _, O) r
join:连接形式默认为以索引为键的左连接- a$ B( _. m- M! M; M  g
na_rep:缺失值替代符号# z/ H# H! c* z# e' |0 D9 J! \
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])4 c) B2 X" C: i/ m% i) H5 i: P
s.str.join('-')
/ o, \! d" P  T& DOut[47]:
, v7 N4 L: x- X0    a-b
! O3 C+ H( V, w6 s1    NaN
; J: x! f" o. C# m$ A* I2    NaN
8 J- j' T4 X. D( y" K' K; W- [  {dtype: object9 M  Z2 ]1 u( d2 f- V$ h5 k+ W
1
% h/ i4 U1 M# y$ g0 ?2
; g, P! O0 }' @7 U9 a, U$ S30 n0 O$ }, Z2 \
4
) I9 w) Z' \2 ^: E( j% R- z9 c( e  D5
7 J% p" t! {0 d  m7 R8 _3 H4 @7 C3 J6
' q1 `4 X( n: [. i1 A. M- T: ?7/ ~4 T# N, J" |7 z' }  d- `
s1 = pd.Series(['a','b'])( L6 [8 P; ^  G6 k/ _% O
s2 = pd.Series(['cat','dog'])* Q# |7 A6 {6 D# Y6 e- Z" O* \
s1.str.cat(s2,sep='-')5 O5 Z  E( K7 M) e9 O- b
Out[50]: # {* f# j% u/ _! m
0    a-cat
7 x5 o) x+ j, b1 p- N$ G7 v1    b-dog
0 w5 k, V- ?4 u1 O# s# b2 [1 W' Idtype: object
' |) S7 O; E# l) k: j+ U2 Z1 t. E& t0 V' u
s2.index = [1, 2]2 r) u" B2 j- G$ u: Y- N: A
s1.str.cat(s2, sep='-', na_rep='?', join='outer')
; L  d' G8 k+ ]: p. \3 e9 WOut[52]: 9 @# d. O4 Y) T7 N
0      a-?
4 Q* `6 p5 x$ x0 U$ @- S1    b-cat8 r' q$ b, W- t* V
2    ?-dog% n  ?, @0 ^9 G1 r% `
dtype: object
7 B+ l3 [. a. S8 y3 @1
7 X" Z) _# e7 I1 j7 h0 Z2
& t* `. s1 j. o/ l% h- a38 J3 I% s  u% i* R* K
4
1 d3 ~8 d7 i! g8 V) S5
2 M/ i! n# \3 W! K5 x, p& f8 r6) j5 h* r6 C/ D, `& i; K( E: Q) s
7
- n0 X2 ]; {* V9 j" L. ?) A8
! m9 H) K1 K. y# \" J8 o' n9 i/ D9" Z+ _+ {/ M) O# I
10% L' \# `' X: f; L  E6 Y5 R6 X
11
- n0 k3 q) T# I. \12
- a( O# a3 v% ?13
  ~9 ]' s) Y5 M+ G  M8 E! k14
# w" ], ]0 a, U* }  g8 {* A15
- c- S% v/ Y( e) r& F8.3.3 匹配7 m% L$ P1 R6 Z( B5 k0 H+ W$ L8 H
str.contains返回了每个字符串是否包含正则模式的布尔序列:
: A0 ?' C. A4 Y2 d/ Ys = pd.Series(['my cat', 'he is fat', 'railway station'])' r1 y  Z$ I7 |, a3 ^6 K$ Q
s.str.contains('\s\wat')
  r/ f2 Y. b) X7 }6 w0 i6 b
( L+ q: m0 Y# O, g) f0     True
" L4 K- S5 l1 a+ L+ m1     True
7 }% _6 N3 R7 s, z  X+ R2    False
, u1 h6 ~( f; M( V8 X7 d, h# _dtype: bool, u7 J5 m% a$ }% z: u5 Z& s2 Z
1
3 B9 x+ t1 E2 h0 P$ G2' d. h7 q( M2 ~
37 y: E" Y/ @  ?/ R; ~. C) i" N
45 W3 E1 `7 n8 W' e) N; Z9 S
5* p% N3 z$ ]5 F
6
# e  Y: o! W6 m7 g) Y7; Z& I  j8 _) h# i( I9 X; l
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
3 V# r2 j# x. |s.str.startswith('my')
$ L$ b8 I6 t8 U  ]$ `. I5 W& c7 F& F$ E  `8 q
0     True/ {% V+ ^+ V  ~3 r! K
1    False0 e" O. ]+ e7 e; y
2    False
/ y+ t( J) n3 H* B- |dtype: bool
' Z' R! B! n! M7 p' q0 H6 t0 K1* l$ ^4 w; z& _: u5 D$ o
2
" K6 c# i" @7 f0 K& b) c39 P4 a  Y, t8 p5 {6 o" x+ M& N% [
4/ A5 @, N) T* l+ M$ X
5
1 p+ m3 e7 c/ A* U6% ~: {- I/ h" e
s.str.endswith('t')
; c. ~/ X4 C9 L/ y1 K
1 U' l' \$ q6 A0 S6 K2 ^0     True
6 y( ?& v% t# e4 A, t9 O1     True
$ q3 t% S3 C5 m! B2    False$ D0 ~* s( M- J7 R. w, z
dtype: bool* t$ |9 Z9 E; M3 R! v) {( i
1
6 r2 g- P, j% L, Z2
3 I- W: C4 `4 h$ }6 G4 u9 A! t3
: v5 q9 q( p2 v# k5 q4
( N6 c2 Q3 z) b- u9 L5
; V& [. G3 N: ?1 u/ f$ L: S. ~8 x6
  R/ }+ {/ c& J3 o; d3 Estr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)* u! v( g+ `* E: [
s.str.match('m|h')
5 {5 |  {5 E7 C" S! ys.str.contains('^[m|h]') # 二者等价* y, V( r1 p6 W( {9 |- M1 E: u
+ X8 y' J! ]' |: r% r
0     True
; a# v8 F+ e% s4 S4 m1     True
" u& n! W; \7 a& V2    False  y# U$ |* w. C3 K# N, w3 w
dtype: bool" t0 x! ^: N5 S" o" M
1; @' }6 ^+ B& H% ^
2
. W4 w. L6 D5 X2 B& b3
7 j5 ~/ i& {* G, ~) n2 f" P4
% B5 Q* d- P* a2 Z' ^5( B% X! ^9 ^( i) |! s! Z, ^
6) Z* t) j- R2 Q* M! }# _8 E
7
9 i3 f+ q1 D9 D$ x( }s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
: {* m7 ?9 c9 Fs.str.contains('[f|g]at|n$')       # 二者等价
% ?- t: N% l1 _7 R% B9 L3 R$ Z9 w3 B
. g8 H% L0 u! a; i9 x0    False. x  j& W- X+ ^; N) c4 ?/ d
1     True( z- T, ?) M6 i' _8 s; w" T
2     True& N- ^, r  M( I+ Q" H2 W
dtype: bool. v+ i; e/ X, u
1
4 [7 F8 y# D$ [9 `2- ^  H' j( k4 F! J6 Y) J
3
) u2 \" l* c% F2 W4
& |; L* s+ Z( M. {6 P5$ K7 o: r3 `/ ^! V3 p# J) f
6
: V3 n  o  W' k: h& g% o1 \- ?78 u: F- j0 j3 ~$ w: T! Z6 P- |3 W
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
8 _$ l5 n% q+ T% ]* `/ S0 J# [s = pd.Series(['This is an apple. That is not an apple.'])
+ R$ s3 {! Y1 p. T9 x! }  l* _
* T) g/ Q& M! o3 z/ Js.str.find('apple')
* c! t! ?7 S( A5 V" xOut[62]: / f8 |( D+ b* {) u/ n
0    11
% A( _% [# n7 d  ydtype: int64- N  J2 x6 i9 z2 q- F/ o: S
# d% G% O. p2 x) s
s.str.rfind('apple')
, e, F7 o+ g7 z1 V& P( sOut[63]: 6 g/ M: W6 U! E" K: o# q
0    33
7 E6 g# K* n9 C2 j5 P5 ndtype: int64' H# M( r% f' s; g4 F0 a- A& T
18 v9 `: U' k( p2 g
2& S/ [! G' T2 N: i0 q
3
& }: ~4 H# e7 H4 i2 |! ?4" w4 U# y7 t7 ~( }# A" t9 i: Z
5) I6 p4 C1 b2 L
64 u; o$ L5 K# }6 Z8 R) f
7+ x$ b. q& U$ Z
8) A' _; j  |& ^8 E: X! C" @( Z
9
( n  Z! w, [1 S+ U6 l10
, h" ?$ b8 I+ C0 z9 {3 o1 }11
2 {: l  }9 {) s7 P& r替换
6 K( [' Q  A# i1 _) F# lstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。3 \# a, H! \& y( f( w7 N5 a: V
s = pd.Series(['a_1_b','c_?'])
4 ?6 T' @; W6 K1 D4 V3 ~# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
* _$ l* b5 c3 C. j& B( c4 cs.str.replace('\d|\?', 'new', regex=True) / U2 m) f4 T" |& ~1 i' y5 |
( ^7 @0 t, y3 V% _4 B/ m
0    a_new_b6 W, i: k# A0 m. z  J2 |
1      c_new
% z. e: G3 L3 o- @0 kdtype: object  q4 o) T2 j  K& H$ Y8 \3 ^1 I" p
1
0 {: @9 s( O& ]. z5 P; O' A2
6 r8 W; P3 c; B, s2 ?0 B3
, L' z; K+ @2 |. ]8 i40 O3 l4 B  _. z0 V$ D7 n1 \
58 h9 t+ w" p; J. b
6: {9 ?6 J% T7 B8 T+ R
7
" J5 F- D3 Y: a6 U; M6 _' l  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):. e  D1 o2 H* R0 h! V
$ o. R" S9 Z' n  a% A
s = pd.Series(['上海市黄浦区方浜中路249号',
6 E7 n2 T( r+ M6 j, p9 c* P7 j                '上海市宝山区密山路5号',% N1 ^, V/ L9 e& d
                '北京市昌平区北农路2号'])
9 i: s+ K, [6 V2 Y+ }pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
: Z% g  k; O- O! Icity = {'上海市': 'Shanghai', '北京市': 'Beijing'}, n, C9 g6 [; I/ y. @
district = {'昌平区': 'CP District',
, e. \! _! u/ N7 B0 c0 e            '黄浦区': 'HP District',
6 N5 ~, |5 D' Y, H+ @5 q& G            '宝山区': 'BS District'}
$ m2 j2 p& H; P6 m' aroad = {'方浜中路': 'Mid Fangbin Road',
  S5 ]" j; e8 N8 z' ^- {. j        '密山路': 'Mishan Road',
6 T: |; E# h; K* u7 Q0 e. S        '北农路': 'Beinong Road'}
/ S" h0 d9 }7 ]! \def my_func(m):
0 e% p; A0 o: x. \1 M    str_city = city[m.group(1)]5 M$ Y( A, s! t0 _" A4 t
    str_district = district[m.group(2)]
4 W6 X% F( x- J4 d: r, D    str_road = road[m.group(3)]
8 K, X" s1 L1 L) j0 [3 G  x7 n    str_no = 'No. ' + m.group(4)[:-1]" L* Q+ t; h2 W
    return ' '.join([str_city,
% {) S9 W" u9 D* h9 O! N                     str_district," Z# h4 |# k1 q0 b; i
                     str_road,
  L- B2 I& l. H( W                     str_no])4 s: l; |% _: C. k
s.str.replace(pat, my_func, regex=True)
* a5 Q! e& w  ~( e3 ?/ n/ @$ `7 }- j) e: c* [5 P: o- V
18 U2 e+ q; a1 r7 d
2% i* p- N; Z: q1 `6 B8 k! e: l
3+ ^+ T* ~7 r; g  W
4
( g* P1 \# n  B- S& u8 P; M. H1 ?& g5
- U- r, M; G8 I+ A/ ~, f6) w$ S) U- X- P
7
# h0 }7 y. X- b' T+ z4 o' R6 c8- L. k+ m9 E7 q8 q
92 Q6 f; Z# s; U" \" l- ^6 i- y  Z
10
( T! p+ R- [, D: B* C112 C( Q1 G/ N3 H) A4 i0 d0 h/ k6 ?
123 J$ h8 }; B; r1 u9 n/ i  r
13
! c; d& z8 U- X( W14
! I0 {1 T8 B; z' @. q6 e' R' a+ ~: P15
# ?6 @! v) c# Q. g1 c: C4 o: {166 t8 J% u# u  I( P
17
: c: v& S6 [) O/ O7 x# d+ T18
1 B( l6 ?+ `* v5 k# L19
6 T# T% |+ Q) \0 \& y( h4 I  `0 w20
$ Q( D, J3 \9 [2 l( B8 f3 Q21
  b+ f8 D* F" W; U9 A8 e6 Q/ E0    Shanghai HP District Mid Fangbin Road No. 249
& V6 l3 a0 ~/ E0 i1           Shanghai BS District Mishan Road No. 5; {9 S) ]9 `3 E
2           Beijing CP District Beinong Road No. 2
0 }4 u, W$ ^( L$ h* P2 udtype: object) z/ N1 D' M7 r( F2 e) }, {  W* _
1+ b/ h3 O% K; G! }3 f
2
! H1 v% Q" @7 m4 ~3
9 C0 l% }. J! _1 r( V4
2 p9 i2 U) o7 X+ m, f这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
7 O) C  g4 l+ r& r# ]
0 O' s+ _$ h% C! L, Y0 O# 将各个子组进行命名
8 \+ |! T; a% ipat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
, S2 S; t* `5 ]* q4 o, gdef my_func(m):
4 q9 s# e( M# n7 {% W- Y    str_city = city[m.group('市名')]( i. l- V; [& T  a
    str_district = district[m.group('区名')]
$ K$ X! E( M6 S0 q* q( c# U    str_road = road[m.group('路名')]& ?+ T$ l- o2 R7 r
    str_no = 'No. ' + m.group('编号')[:-1]( v% _3 z, L0 H  l  l0 w
    return ' '.join([str_city,5 o3 d3 C2 i7 v, k3 }$ l3 V
                     str_district,( k0 K9 p- E: Y5 p
                     str_road,
$ q" @1 o+ N. k: D1 L                     str_no])6 q5 e# \- E4 Y8 x
s.str.replace(pat, my_func, regex=True)1 `; f, `0 a# s# i6 G
1; b7 ]3 _0 s: V3 k$ w* G4 g; N4 S- c0 i
2) y" t% }+ M- {" `) X- M
30 W4 x/ i* G! s; n5 u% L
4" T7 t' _6 P, h1 q; y
5) t- j8 X! O0 ^, L- T% }& a2 c
6
' p* R2 ^3 }) }3 P& V7
, f7 G( `2 k2 N/ S82 a% P* ~% {% a% o+ i, `( J
9
8 T8 a5 B' s8 o2 U* Q, k1 F9 X106 a! T4 s8 x0 l$ G
11. A: l" j* B1 N7 M% H
12
9 Z, R" O$ ]! K+ N# a: m# J0    Shanghai HP District Mid Fangbin Road No. 249$ \7 q  p# h+ [
1           Shanghai BS District Mishan Road No. 5
% R* U+ I2 N- E9 E" y4 ^) t' I2           Beijing CP District Beinong Road No. 2
7 A# G0 Z( E7 T+ K, e, Z  h3 e4 Fdtype: object
6 R4 f1 x; }8 l; _; G" s9 @1
; @3 [2 B) l2 g+ P1 y+ `5 p20 R5 B+ z6 [' P
3
+ z% q' S3 x, g' E$ ~( P4
9 R  s7 X( u& N/ U; ~  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
' M$ H9 b. K1 Z7 }, H1 o$ D
* f0 Q6 q2 v% ]$ S8.3.5 提取
9 ], l/ [+ L' z: k8 T8 h0 p3 vstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:- f1 {, M8 A- Z2 [) N! |& z7 r6 y
s.str.split('[市区路]')
9 d' S- ?  @& N: POut[43]:
3 e! Y. Z5 v7 s: p% p& \0    [上海, 黄浦, 方浜中, 249号]
" Q# R- E' o! V8 L' l6 H+ v1       [上海, 宝山, 密山, 5号]
  d5 @9 m5 T) @8 D9 Hdtype: object; a3 }1 z/ ^6 R3 {' ^
2 c- _1 d8 M7 p6 a! p" C" P0 F  t4 C
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
- L3 J% _) \4 x% A4 h1 o  Es.str.extract(pat)" X% H* A- }& F5 a! K/ S
Out[78]:+ Q3 j- k1 v" q  k! f
    0    1     2     3
, M- v, q9 w5 {# o* i0  上海市  黄浦区  方浜中路  249号% N7 D6 b* I; d3 K& I  e6 y
1  上海市  宝山区   密山路    5号% T7 l& d  `) v
2  北京市  昌平区   北农路    2号$ z- M7 a% c5 x! q$ p. J" q5 h8 h5 w
1
' w) B4 k- G* I+ m2
, F. a7 J8 a, H7 i  x3
8 @8 O7 p2 k* t  g8 U& T- y4
) G* }- _" }' U; r) Z+ l* f5% ~# w( X8 Z. W2 n* T! N
6
' ~  I. f$ m: o! s7' _2 o7 I0 M: h; p
8
" A- r1 k% a  k, s9
7 ?. }+ X* A7 t: w+ r2 y' i! I10
& @: A3 l( f! j' K" v4 t% r11( q2 z! E$ \5 a% [
12
2 K4 X( R5 W- U# j  \& f% u; x13
' q8 T1 q0 z$ o* N6 l/ M" z通过子组的命名,可以直接对新生成DataFrame的列命名:
" d; K' ~1 H* r! w( ~$ t! a1 X, K% G
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
" H( T! J( L1 O" G7 As.str.extract(pat)
! w  _" [8 i  O5 }Out[79]:
" z& I9 C$ X# Y# x: J    市名   区名    路名    编号
1 V  N/ @0 `! p5 f& z0  上海市  黄浦区  方浜中路  249号; g! y6 j8 r# A$ E
1  上海市  宝山区   密山路    5号8 s$ M7 C! i) f/ i
2  北京市  昌平区   北农路    2号6 w9 d! i  g9 A
1
' m) S: [% G3 a. g2
: J- O" o1 c7 b# R3
" |* e. n9 d+ y  L' M5 L4# W7 [% {8 J4 q% G3 t  \& D4 @
5
8 @5 G* f9 j& a6
( M; V) E2 K9 s, P5 p5 h) M& [0 G. C7) q' y8 H3 k) v2 K% K8 K. s+ r; B
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:6 d2 l* Q$ Q! s+ {' }4 h1 j
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])' w2 m& s5 R8 k$ ]! W
pat = '[A|B](\d+)[T|S](\d+)'
3 x- ?  w8 `7 b9 C7 zs.str.extractall(pat)
7 r5 T& `8 B$ j9 gOut[83]:
6 s( a) u- B- Q2 R" Y( i! T       0   1
  F- x3 ^" X& C! I     match         . A, p9 d5 l3 y) ^/ o2 C: W
my_A 0      135  15$ }$ J3 n$ h$ j7 Y* k- D5 s
     1       26   5& i7 f& M& v) \  u! d
my_B 0      674   2
- ^8 o3 t; u' k$ j; B0 z     1       25   63 `' H8 [( E' v3 G3 }* Q, I
1
0 x6 x6 W9 x  m- N2 N$ r: S! Q2: J5 l/ k1 I6 r
3
) s# M; B% m. v3 n5 P) r. L" |4  W/ A. `3 g- h  l
5  _& A% G5 T# _1 b
6
! L  Z2 Z# m3 @' n% a. y3 P& l7
9 ?- L$ d, c% Q+ t, u% u8: d2 T1 \$ ^3 `
9$ V; ?4 a' P5 F; m  P3 d7 k
104 X4 D8 Z& w& C. l; ]7 X+ F5 _  T
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'1 d; W  K4 m; ]! I" B' H# F
s.str.extractall(pat_with_name)% j6 \' f# K% b" [8 I0 s+ r
Out[84]:
* X  s; T* ^' `           name1 name22 p* _, ^: ~/ W2 z: g
     match            
. x6 V" \4 V1 N4 Q' x  fmy_A 0       135    15# ?" z5 @5 S7 e6 E  C4 k
     1        26     5
3 C& h9 V0 x% _4 n) \/ Smy_B 0       674     2
7 L9 ~6 U. [0 `# E$ X+ k! @6 ]     1        25     6" @6 i7 R' c8 n5 o: h  v& O
1. }8 a- h# N6 g, Y) g' K7 {
2
( }5 ~& h+ @. x% ?8 a, s6 F- _1 g3: h8 h2 ~% m$ E/ G6 A
4
/ z; M3 Y  _1 N) E- t5) s: I, ?7 a5 o' Z- a
6, o6 ^7 D4 ~  |1 p  G+ l1 Q% C
7$ [3 ~0 ^4 \3 Q; V
8! l4 h  K# H! x- t7 U! |+ O
9
: Z3 ?7 F* g1 S- V8 W" G8 }str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
, U/ ~$ Z6 w6 v# M* a+ Rs.str.findall(pat)/ d. S# R: P6 r- b5 P  v7 w
1. w7 L: ~& q# D8 @7 I+ `
my_A    [(135, 15), (26, 5)]/ ]4 P: z1 t8 v" X7 [
my_B     [(674, 2), (25, 6)]9 @/ ?: E5 H0 H- K% I' u# e
dtype: object6 }( o" T2 C% X3 ^, W6 l7 p
1- q! t+ [& L1 }* H
23 b5 t; u3 @3 z, W; x
3
( ^! q7 S3 V) t, m; D( V" n8.4、常用字符串函数
* W- e. L% i, Q  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
6 f' |* r4 F& z8 r
8 a. I& }  J; X! t' D2 _6 h8.4.1 字母型函数
0 e, d* m; o+ G, V9 o; a  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:" ]" ^( W! y' f2 S

. Q* x1 L) Q$ {  I0 A" j1 ss = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
& K; q$ M2 J4 o# k9 ^) V" F- K- d7 {4 J8 \
s.str.upper()
4 Y& e! j# Z8 |% b5 qOut[87]: : @. l: S* U2 c) f5 X( \
0                 LOWER
( L9 Q' T- B, d4 o, I1              CAPITALS
( p. K' }: q' v9 @4 O# G2    THIS IS A SENTENCE& k( `. |+ }( w+ _# c
3              SWAPCASE: s& J) ]* R. l6 W
dtype: object
* M/ d" o8 d6 w4 \
  W8 N; H/ P. L4 {+ Q0 i9 Os.str.lower()% w* q$ t" U( }6 i6 n
Out[88]:
- n5 _# G/ D$ ?8 m& q0                 lower+ @5 G! H$ V0 |1 a
1              capitals
+ Q+ l2 ~5 t  V# S2 V- Q2    this is a sentence. V$ x5 M$ u3 g4 x; O6 O
3              swapcase
* B3 B3 `/ h& a) V" c8 m7 D5 mdtype: object
: `# `& p: d* V! C
$ t, T' b0 D& N4 C) ~* c# z) t* ps.str.title()  # 首字母大写
; J' U( V' j; l* I3 o: z+ \4 ~3 LOut[89]:
( {( S. {& S* q0                 Lower+ r# L) _( s( X0 p8 e
1              Capitals& G% l5 P$ @/ Y1 l" _5 d
2    This Is A Sentence  J  V. G  l6 t0 N0 H
3              Swapcase
  F. |, J( f6 y$ Hdtype: object
' D; r, s; f( p4 J6 @
: V, Q& _) A( i6 K0 e. x: o  Ss.str.capitalize()  # 句首大写
# k3 S0 E/ W& UOut[90]: ) c6 B" P' p, b2 r7 ?
0                 Lower
7 }3 N' a& C8 S2 `; F; e" e( {1              Capitals
9 x" j: y" D1 A/ ]- v$ }; p; I2    This is a sentence1 `) @* X3 L2 A
3              Swapcase- T- S5 F) D8 s+ K
dtype: object
  x" a$ f$ h; A+ s: v+ W
4 p" ~  O7 d' j4 Bs.str.swapcase() # 将大写转换为小写,将小写转换为大写。
' Z) A3 O/ {/ ~& J& ^Out[91]:
9 X# Q: d2 S; W7 y- I0                 LOWER* i: A# S0 G7 t% e
1              capitals# j0 @5 l7 b, L% [, F
2    THIS IS A SENTENCE
3 X3 {- T" ^5 k0 \$ S1 Z3              sWaPcAsE& f" s$ C5 ?: m0 N) G! W4 Q" v$ A
dtype: object
" D: g* i" n4 ~4 \: F6 ^3 I
. \2 t' }/ k  y/ X/ Hs.str.casefold()  # 去除字符串中所有大小写区别
( E' B  _; b) ~3 u5 O2 N
  G/ i. O8 ^- I# H, V0                 lower% q; g+ D6 ?* M: m4 J6 r
1              capitals2 w( D  ~+ y! g7 U
2    this is a sentence- r) a3 s1 X* E
3              swapcase: S' }( B7 ?5 A9 L  k/ D
) E  o3 o1 O0 i: x. S
1
; i% \$ ~2 r" c* i9 b1 M  x2
3 u, f- j& A. U36 `/ |8 |& @0 N  K  F
4
$ b# G$ j) [7 p5+ c) i6 ?' j( v2 }& e. z, s
6# L( k' M6 |& G. V' v% {+ N- z; i
73 k4 ~$ U7 Q( f; q, P# X
8
' o$ k# I  B5 N5 H; Z- }; A1 x9
. t; z9 `; }* Y2 E108 L; }2 ?1 I: Z4 R% D$ V
11
5 x* o! p7 N8 x  S, Q  G& x2 K12
; N% ]8 v. D0 Z9 }  t. s$ `139 l9 T7 [: @' s$ l0 m  f& p, Q
14
1 o2 \, V7 F! K. F8 x2 i3 A+ [15* B# v4 h! F. k. [: h" ?5 h
164 b3 F8 K2 H% K, J
173 Q) W2 l! s- q. h# J$ v4 H
18
) x5 k; _% ]% T7 b19
% O  \; D0 j% w  C) d; p20
5 X, a- f7 i1 X- [/ a6 M21
! ^& P8 W+ n( O% S5 j22
0 v# N) m8 S/ w& G1 d2 I  _) s23, ~7 A9 ?  K9 z. b) z# _/ S! X  d% ^
24
& R; L7 U& G7 f" {5 ?! \25
7 F/ ^. m2 U/ w2 V: Z+ W26
. X* e6 ?3 m) ~" Q27  x7 G/ U) Q6 k6 s
289 U5 B0 R4 p6 t
29' t5 }7 {8 p$ q) C! x0 W  @' c
30, G$ @! c1 h5 D, t* Q# d0 s
316 f8 O1 J! S  G' E" I
32
9 l( j- o. o# B5 c) B% c5 Q/ s33: l( Z  W) g' m3 T) y8 ~
34
  l; h' z# P9 Q% N6 G8 M% ]8 ?359 ^3 L. k1 k" C( a& R
36
. [- Y: n% t! P8 {  `372 w' |' F& `6 O: N7 Y
38
& p6 t5 N3 b: q. P! e39
; e7 a2 _% Z3 w, Z# n; K40# N. y) t' `6 a3 y5 X- `# q
41% W/ h7 d8 V7 c" z1 w4 _
42
. t% k9 G  u! Z43
/ r6 C% Y5 k3 P9 P4 O" C* j: x444 X; x( v/ p' T1 d; V
45
7 X# D* h1 q+ W& t3 v46. s/ P( F2 C3 e& {
47
* |3 r" ^1 B7 \: m48
8 Q# E, a0 r# r& x8.4.2 数值型函数' V$ \$ l- l3 v$ e2 n: Z
  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
4 P( D* @( B4 \- x9 T3 N6 k
; k) U/ _8 q( _% y! l9 g/ Xerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:8 {) ], t: g0 z" b
raise:直接报错,默认选项0 `3 h4 R' P2 R
coerce:设为缺失值
4 ?5 y. o1 D3 \+ e- ]- |ignore:保持原来的字符串。
+ H% u* T. u& s  ?1 _downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。8 v8 d0 \0 g. }( }- |  R: I( O( X" Y
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
0 j) Y! f/ N) M5 o
/ s; ?4 f+ r* u# Lpd.to_numeric(s, errors='ignore')
* V2 g- f' A4 e1 b- |Out[93]:
( W, Y: U8 H! v; f6 Q' a0       1$ G6 G5 u6 f1 b6 i% V
1     2.2: n0 P9 Y- D1 p$ s
2      2e
' ?- B9 x, z5 Y- Y: w3      ??, M5 o9 z9 d' P# A' i0 m) w
4    -2.10 V, }( O4 N# n/ y- m1 z) u! v# M
5       0" B2 l7 E0 ]- N7 w2 h2 v) {# Y
dtype: object# x( g0 i. g1 Y7 @2 M
1 s! Q8 @1 k3 T# G. s
pd.to_numeric(s, errors='coerce')
, s6 Z, H& L$ p( S/ E4 k& ?3 bOut[94]: $ q5 I+ c) p+ a! A
0    1.0
  Z0 @* q1 W" J! z& a( d2 O1    2.2% K" X( _" C/ V
2    NaN% L' L7 b, A. D& |
3    NaN- O4 k+ b1 V& r$ s4 l* e( G
4   -2.1
, x0 a% o9 |- T8 b6 o' E4 l* E5    0.0
$ L6 y- a% {, l+ W! @( t8 Ydtype: float64
& H. g, d, y& d4 A& _$ L: W' Y
% @1 c& m! A9 H) |; t2 H( N8 N18 d1 m' [( Q9 l* i" M  Y% O) Y
2# s' x/ q; v8 e3 Q7 o  e4 O4 R# i: s
3
. S. q/ @# X" D7 F( f41 ^) v2 @$ I# D3 X
5
" h) J1 J' W# z% i' B0 I6
& v) w. b$ Y' t+ L7
, \, P( f, Z" _  [4 d2 p8 u2 \/ P8
4 k# F% r; K3 A8 B" u95 w# n# N/ p( y; X1 y+ X0 a
102 f& @7 O4 e/ j1 i4 {& s
11
+ n6 Z/ P. M+ U  d0 I# R12. @0 M  |$ w1 r( C
13& P2 w% Z: H6 V% o3 V9 U$ q5 l3 Q$ f
14
3 U0 d# {) E, x. P9 }. e15
0 A  r' g; p5 T% h+ P. k. @5 Z/ m16
3 g2 g9 [# [2 w6 f. k! D7 o- x17
+ E  S8 m4 N$ n+ @( ^187 [+ W, D. c% B6 }' c8 k
19
* G: x+ ?' x' [: z9 p  z20. w! b3 N: S8 p. R" h
211 C5 @6 j; E3 d  i$ U  ^8 }
  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:- D6 {  V, c8 H$ [  L

6 X9 e! o8 ?2 ?2 n1 T1 ps[pd.to_numeric(s, errors='coerce').isna()]# E$ T$ h  m( {
Out[95]:
! i& N! V( D! o7 W6 q, ~% \2    2e  H$ B0 Q8 |8 ^; y% C0 W; n( }* I6 A
3    ??+ E4 z0 d' y) W5 J  t7 x
dtype: object
& N- m- P6 K5 j& E  Q1. e" h' n& o( Q, O& A
2
% T' q( j( E. a& k, g4 ]3
& ?" t& Q  [" z/ T" Y- H4
1 {7 C( R- x4 M! {$ K' ^0 ]3 T5
, F( `! F% g9 y! @8.4.3 统计型函数6 V3 q2 c4 W5 m# c
  count和len的作用分别是返回出现正则模式的次数和字符串的长度:9 B* K. p# b) O+ F0 V2 [
, p0 h; d/ Y/ @; Z' a: s
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
7 g% a2 E4 e2 |8 F7 l8 D% g3 r/ m! B" v3 F/ O' f
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
1 P8 i. @. p: a: y: @/ F5 m5 J6 hOut[97]:
4 N6 [" q7 z# f5 a1 z+ L* J' u+ _0 t) Z0    2
6 _7 w+ K& M# q5 b& G1    2$ J9 D+ R- n9 y9 w
dtype: int64. k3 u6 ~' }2 o" z

. J- }1 j$ {* Y9 Os.str.len()
$ N' w* }! P6 U# X- X# P/ [1 FOut[98]:
- G* U7 y, r* Z7 l0 n# j5 i0    14
; A1 ^+ N, u6 f0 }1    19
  T! F+ e6 x' P& r4 o6 Qdtype: int64
3 x+ q1 a9 i6 U& M6 o1
6 f# \/ W) J7 l22 {( h! O9 R3 B2 I+ t1 o0 Q
3
% k; }- K: _- L) Z5 x44 C) ~2 ^  ?! b9 r
5
" D6 D4 i$ N* ^/ J6; d0 R  K+ C* k. d6 j# }! T$ q
7# T5 m) \' y% B
8
9 ?  u  F& r* S# E/ J9
; z: H/ ?- T# ]* G- c" M- B108 c3 M$ Z  Z* f" t& a+ ^$ R
11
' S8 C7 w9 e/ G5 l' u( w) k3 I( Z12
3 o3 z3 ]" `4 d0 ~. P  L! ]13
( x4 W6 u5 ?  @; i% H, C; z8.4.4 格式型函数
  [  V( T, M/ B8 d4 u4 {. D  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。# r# a- w, B2 u$ x* B7 v3 H
5 g# l$ D1 i4 p
my_index = pd.Index([' col1', 'col2 ', ' col3 '])# \- m+ D0 H# M3 y& A2 X3 Z
/ U0 G. m' G) A- _0 G
my_index.str.strip().str.len()
& Y# V1 q* q8 n5 nOut[100]: Int64Index([4, 4, 4], dtype='int64')1 l0 s2 T" b/ i; y0 G! G$ J2 V
" k) ^4 q8 }5 L7 s/ U
my_index.str.rstrip().str.len()3 G" N$ Y9 k6 X# S
Out[101]: Int64Index([5, 4, 5], dtype='int64')# \( V/ a( o0 N" ^

/ G* P: Z! F7 [- T4 hmy_index.str.lstrip().str.len(). e  _  ~# q( s% @
Out[102]: Int64Index([4, 5, 5], dtype='int64')6 T# \) i2 A- R. P/ V" F
1
$ {* v2 V2 j2 o0 l7 Q0 [. c4 Y: U26 E4 t3 h8 l4 B# [( R* W1 a* `8 `
3, q; q7 z/ t& H! S0 @1 ?
4
, ]3 U) u2 P+ T& G2 q) c5
5 P, B6 y+ O: x( Y6
  W; S& E* U/ C) O" K9 o7 S  ~4 h8 V7. A0 z4 U! q1 t- i
8' e$ e' [8 u* N1 b4 o, M
90 ^. r( z3 N5 G' r) b& f
10$ O( d8 L9 f* ]+ x5 X
  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:( s/ e# G% c, C

: X) ^' O7 S) t- T9 A% Js = pd.Series(['a','b','c'])' {3 ]4 v# S* r9 I1 `
( o. D1 @1 w  }" s4 Q4 V( Q7 x! q6 I
s.str.pad(5,'left','*')( n! a( {& j- ?3 @, ?2 P
Out[104]:
- G8 }; p) t# M; z% L0    ****a  _' e% b9 l% _/ G
1    ****b
  T: \) f  {8 ]5 C8 c2    ****c1 l6 g# R7 ~7 h4 Z, n
dtype: object$ y9 |4 ?% l# w6 P6 h
' R/ L2 V7 y2 N0 {) ^- s
s.str.pad(5,'right','*')
) ^+ A% p) w7 c$ T  t4 X3 UOut[105]:
+ @7 v* P% V$ u, {0    a****% K8 J, j- y/ j2 v+ S
1    b****
8 B; m9 G% _0 n5 k) P. Y9 m2    c****- \  ~9 I5 G6 V2 g) W
dtype: object1 r: E2 C+ H7 }% @

/ l5 V* g1 d( b: M' A  M# hs.str.pad(5,'both','*'). I) m: Y! r; r$ A
Out[106]: 1 r" x  V7 s$ x) h  D
0    **a**1 Y) k$ S6 A% R1 V
1    **b**$ y5 c$ o( M5 H1 a" P
2    **c**9 F/ c( @' P% n4 i  M( \( H
dtype: object
$ S# ]' Z: \$ q% U# n7 Y( g- y; m3 Q( N; B6 d, W5 v. j/ {
18 C5 z. l+ W, o
2
* C" F+ V5 |# V( D- i/ D" Y3
+ [) A' P) S3 y" ~4
. e4 q0 S; h6 M! u( E) \3 i5
  W0 ]) Y) Y) s3 {. W' Z6, S2 u' g) @2 n% a1 s
73 e7 \  z6 C* h7 o8 q
84 _* H6 g, m7 W- z4 e- j3 N! V
95 ^2 A/ V& ?2 K
10
/ P* n9 V! g* b4 V+ W, J; N8 l112 W5 }5 G5 K0 _1 l, {/ r( ^5 x
12
1 d! _4 O! r) O( K13" G: `/ [4 R" D6 M1 ~
144 d7 P" X& i5 Z
158 H  `; R3 j8 ]$ J. t/ G
16
  E9 F8 m1 w. R7 N# O( O  \" I174 \1 T5 s8 r/ H. O2 W
18( J; D4 }+ z! F+ p
19
+ T% X/ C/ T; w20' X, @3 x4 u0 J
212 i6 C! F# c0 C4 G
226 y/ K. E% ]9 j  L5 T) k4 \+ ?
  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:) n8 G1 K- w3 }7 O3 g
* Q/ z: ^8 g; d: i( l" `, \* T% i) G
s.str.rjust(5, '*')' `: Q0 T: v8 |# n6 ~
Out[107]: & B. X; N2 N; z: c/ _
0    ****a
5 b4 I, `2 T# t4 o3 t3 D/ S0 i1    ****b
+ B3 z. f3 ^8 ]. x0 F) b2    ****c7 l& M5 b$ p( G
dtype: object8 j# V! B4 `5 a5 S$ F% l8 N; d

: m6 Q+ h, u3 gs.str.ljust(5, '*')' f9 F5 S# ]" e0 q' f
Out[108]: & v! R6 L0 Y2 n# P, J
0    a****
0 Q, q. e+ d) r( S8 Y) }1    b****
2 w# Y* `# x/ V2    c****4 r5 |" S% q5 H8 w
dtype: object
. n* [* |3 p, T. [+ m) F7 y4 V# @0 a. k; L
s.str.center(5, '*')% L! }+ x' s7 U0 O, ]
Out[109]: + W7 @9 N- ^* `) E" F; O0 A  h
0    **a**0 K9 u1 H: f$ p1 R$ X1 K0 w$ v
1    **b**
' [% o2 G) A# y2    **c**( O& o7 f" m+ J
dtype: object
' o- o! b% [2 r, r
- j; D* n6 h5 ^; e6 w1# ~& V& Y1 F* W; L7 F2 g. H
24 \! s6 d; T, H. k6 G. I
36 D. q5 p. A9 W2 Z* M3 g
45 J! O8 d+ a$ W1 c
5: Z2 P1 m- _( Q3 Q, @# U  i
6! O% ~) f0 ^4 Q' P
7" N# d5 c3 b/ _$ B) A+ f- v  e# A% \
8
3 y2 E- b$ X% B- T) d99 g! W% L6 B) H0 c) I
109 d# d' S* p" ]. j! {& U; C- T
11
. U8 L: x, \. `9 _! _12
. S7 T; u' t9 C4 E+ _0 E' _13
8 \! e- Q. M  `$ Z' ]% O148 L2 e/ p3 v: U' Q- ?* G
15
2 T8 c9 r. t' C, q8 ~165 I! T7 o  c& Q6 t, T8 G  }# R
17' E& L7 T, w4 l& k8 `* A
18; h$ k& ]* E# M2 {: b2 ?
19( @1 l0 F' d# [
20
; D9 h9 |) P& a8 J  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。% V8 Q: l5 ^# Y  l- ?" h# e

3 u: J1 x* l3 z8 }( [s = pd.Series([7, 155, 303000]).astype('string')
( m) b: }6 r& \; D& l  J- T
+ Q9 u* e  l; ]6 W- Z+ Hs.str.pad(6,'left','0')3 C" K) F0 S' Q2 q! K
Out[111]: 2 X1 q7 p) A) q, l% U8 O3 G
0    0000072 t- I  v, i6 ^, Q
1    000155
+ q$ }8 n; m; @( @9 {2    3030005 O; Y. V% o7 w4 i( Q" C
dtype: string+ z+ J! Q) h) C; w9 n7 \6 \) \! e

* u8 z* F# [; N) ps.str.rjust(6,'0')
( u4 x6 b9 u$ O$ Q: A/ Z% F0 e1 YOut[112]: 3 j3 H% x0 I# }1 \1 W
0    000007
, w# m. N2 |5 h# w1    000155: Q2 d$ C: f4 f+ v
2    303000
1 L9 z/ \2 n; S# G, R$ U6 Udtype: string; R5 p: N' `  O
) l0 g% Q3 N; w' n
s.str.zfill(6)
; M1 ~! U! G0 b9 EOut[113]: ; s1 w. ], H0 e
0    000007: L! q- }; |" |
1    0001552 e) ~& J2 F+ C, m- y
2    303000. B2 v3 X) x$ l" Y( ?
dtype: string4 M0 _0 M3 ?; F: T/ y% G
5 H# J& c! J) _
1
) B# S$ Z' f* N2
2 b7 n" {* c7 |6 k3
( k+ i% {0 d# x% r1 A4
6 h2 S- S; o* \$ ]5 B* \0 v5 r5; k4 r  g+ A, A; U
6
0 s# L! H' O. ]  B" ?7
9 S$ C7 y# Z* z9 j* \8
, ?7 w0 J" d* v9 y) l5 E90 B7 Q6 a* ?1 p8 ^
10
  k) x0 r: F# L3 J11
; n: c/ f% C# d% Y9 w/ X123 K" r! C8 e5 l, |! W; g- D3 R# w/ D
136 d' @/ T! t6 u8 M+ y5 b
142 f2 E3 F  ^- k8 ^  i; Z! o+ [+ y. @
15
" C- k  Y+ R1 A3 U" E% q16; i" |$ I! e* y- q9 N8 Y2 f
17
' @" Z( h1 Z- _' r- |; T3 d, Q18
* H: Y2 Q% n0 C6 z! ~/ g190 I' ~! d) e9 X
20
% j! b& R+ ^1 K4 x; U21
) [: p7 l# q2 T1 x& [' l& k5 J" r22
8 e; v, b6 g- o! q6 f1 x" c8.5 练习
. X! |# i3 i+ J+ tEx1:房屋信息数据集
. K: f# h  j( ^现有一份房屋信息数据集如下:7 s( Y# o' g& y0 z/ e: b

9 E4 s2 b9 c  F1 `+ w+ ?df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
6 y4 u  R9 y# Y4 ~' T  {df.head(3)
  x5 k3 w4 G, p, gOut[115]: . k8 M, m0 `! M. Q
      floor    year    area price
6 z8 }2 J; m$ k1 C) U0   高层(共6层)  1986年建  58.23㎡  155万
7 d$ G3 f  ]7 {* q6 B1  中层(共20层)  2020年建     88㎡  155万
% L! i# ?% c, |( U2 P" X3 G2  低层(共28层)  2010年建  89.33㎡  365万
  H* l* J$ @# h6 u/ b' @  K1 Q1
; Q4 d2 r% |5 k& F& w2
) W4 F6 ?/ |3 u/ x3
) Q6 x! l3 A4 e4; U; D. u0 H+ M5 ?* `. D" N
5
* ?- v4 p: `9 j) F: j' d$ [& W6$ _. g* Z, T  h. `
7
+ @8 q+ q+ e7 _- b将year列改为整数年份存储。5 M" K! T& y  B% t
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。- V' C% x& E( `1 J( R% X2 t
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数% v% \/ E" f% a) X, q1 n1 ?, b
将year列改为整数年份存储。
3 h' _9 U$ S+ O# Q"""
) s* n% t$ {  q, }* [9 Q1 C5 Y整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
5 u* l/ G; c. }) g6 T注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
: V  c7 c/ p/ `- v. i; b; z3 w转成int后,序列还有缺失值所以,还是变成了object。0 ~5 i6 B+ M0 {: ~
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
* N9 s; F! R7 v: A- R. ?( Y) x"""
, R( ]* q& p2 g. Ndf = df.convert_dtypes()5 s9 D: _# ^- M
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
- a7 e# V0 Y. Z6 X. W6 p! |df.loc[df.year.notna()]['year'].head()
. \" R' n. l& s$ `& f: h
  ]8 b) L. t/ ], B0        1986
, C. i5 D, \3 h9 x( z- T1        2020! N$ M" S0 `" P
2        2010* L% Y# T! A4 u  O+ v
3        2014
5 n' r0 d% Y' u0 I4        2015
9 l- n: b& C5 a% L; }Name: year, Length: 12850, dtype: Int644 k6 W- V. d+ R& i, `! n9 Y( O6 o
1 C7 ^8 ~6 R6 y/ ^& c
1
$ K8 ?# q" ~6 p2' z5 k5 _/ A7 U7 r. z
3
  H, c1 U! W0 A. _" x- k4. g' i: d3 [! m4 P5 ]1 L
58 B/ n" i, I: M- C
61 C" s1 q) ?4 a& J
7
5 R9 p, e9 y6 }) B; p8
# t* W: ?3 L4 a; r1 t0 p9
3 c8 n5 U- r$ h+ k5 y10; k$ F# Z. a7 E' h9 _% B0 A7 N
11
% v7 n- _; U' U) \12
6 [% X8 q" C  W" X13
0 j3 p* p& J; a. r/ R: K1 j* C/ M7 h14
! V4 @% {  U3 }# r& k7 f155 U& P$ b7 G. d  n. Y, r
16) X5 A( x+ }5 x
参考答案:- B4 T% I0 R5 a- Z" ^4 I

. e2 c5 Z) g( x% }% e$ e7 N/ w不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么1 l8 ?# {* x* W( S1 u
; Q* |; f1 V9 z+ }
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') 1 w. b0 m: h) b' a# N, Q6 f
df.loc[df.year.notna()]['year']
' P" t% C" J, _4 y12 Z# i. e7 e) h% a) @$ B+ s; d
2
0 l7 u7 S4 L, c+ j6 N3 d将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。4 ^% r0 C/ E& z8 t  h; r$ s, Z
pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'+ A8 O$ ~5 b  O. |5 c* ?/ p
df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次% @4 s$ U) d9 H3 g
df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型8 f: Z$ l% w# v* e9 t* E9 F
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
( d$ H; X3 ~5 Y! ?" @; Kdf=df[['Level','Highest','year','area','price']]
! I" G( p  y8 L. cdf.head()/ m; I6 }8 N3 ]+ k
" t% h1 c3 T9 J5 A2 r
   Level  Highest        year        area        price2 }/ p4 W& [% f) H: x
0        高层                6                1986        58.23㎡        155万
( b/ r5 R  [% }6 I. d% j4 h; G1        中层                20                2020        88㎡        155万
  f3 f, o5 q2 n3 \2        低层                28                2010        89.33㎡        365万
& r+ ~1 R1 h4 G  V3        低层                20                2014        82㎡        308万3 a* p: }; N  p8 q6 H9 R
4        高层                1                2015        98㎡        117万
, f5 I; h7 ]" w8 I2 N2 Z2 {1
$ r) f/ |- z4 T  c6 n$ Z24 a1 ^1 Q; \  Z6 z( X  N' Z
3- b0 P9 d) Z: _7 m+ Q' H2 W3 D2 r
45 F+ p; O6 |; l9 q) ]; n2 ^6 l) @
5
- |- x1 a; r7 T$ p69 k# q0 N$ p. D; G. M* k& f' H' l
72 R4 _+ N6 I. [) J9 r. Q
8
+ L# J. N4 m# _/ h9 a9
) }; G  r0 d% A; x8 @. D* x" @# m10- s/ h  c: {/ G' G0 s! D; P* i$ `
11
: {, t% }4 ^1 o3 ~2 p* K9 n124 `. {8 K6 Q1 Y' S! b. @
13. r1 L# o$ ?+ r; b' Q! f2 g
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了7 O' F) g6 C# |9 M3 f
pat = '(\w层)(共(\d+)层)'4 I0 H8 x$ ]& ?) C, ^2 y
new_cols = df.floor.str.extract(pat).rename(9 d4 v$ r& h9 ^- W$ z0 S+ \/ E* j
                    columns={0:'Level', 1:'Highest'})) b0 p' x* X4 U* m; P" N/ w

" h+ e  X- F# V# H* H+ x$ Q6 Zdf = pd.concat([df.drop(columns=['floor']), new_cols], 1)# ~" d9 j* L  O6 F/ N( `% W5 w. L; G4 G' U
df.head(3)9 M0 h9 D% i) g) J/ y4 i$ P
7 q- N5 T0 G. P0 B$ O* \6 H1 `
Out[163]:
% X( K3 f5 e# k7 a% m5 I% e   year    area price    Level Highest2 l4 m& I1 {+ f
0  1986  58.23㎡  155万    高层       6$ W; P6 d( z+ M4 s' m( r
1  2020     88㎡  155万    中层      202 _% p& h9 Z/ L* H# K4 U' Z4 l6 @7 d
2  2010  89.33㎡  365万    低层      28
# f8 S, L7 j' m! R/ S7 _( |15 i8 M! \3 x) g1 Q' o1 b( q
29 I% ~& W1 ~% A( n# x
3
  n' ~8 O/ N% c% l4
  R8 ^3 ^5 k. F, |8 z5
& N  n' g( u/ I5 s8 m61 S9 P9 u0 {8 M# F  z
7" W/ A, n! A3 S4 E0 A
8- m- `! b4 F2 ~; e; y- I6 S
9% T" v! D$ T% ?: X
10) c* U; `$ ]' M6 F
11
1 H$ W" Q" ?* G12
; o! y! v* e" {- l) Y' l13
; n: X0 B% E0 s/ d7 I! K计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
. c9 b( F9 \5 J3 w+ T- o"""
- r" \4 U$ k4 u! e7 S1 n/ t& \5 Q# Rstr.findall返回的结果都是列表,只能用apply取值去掉列表形式
" b# ~; C5 f* k' _4 u- i8 l参考答案用pd.to_numeric(df.area.str[:-1])更简洁
7 n1 U; e. w9 f3 P% p# m由于area和price都没有缺失值,所以可以直接转类型: \) p0 c: i6 U9 m, E: ]
"""- H( M- k! I/ P  N& e- H
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))' g, [' r6 Q3 z- x* R6 K7 ?
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')- p# O4 u) E' }* u, x( `
df.eval('avg_price=10000*new_price/new_area',inplace=True)
* M# o* [3 U, a4 a8 X3 z# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
- Y2 [( _/ H9 {& k/ P% U0 K  h* W# 最后数字+元/平米写法更简单( v( O- A" W1 T
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'1 b& O8 P  g6 z. g4 x1 \
del df['new_area'],df['new_price']9 \! V( l. h, b6 N! K
df.head()
9 ?  Q  @, k( U9 T+ L
" c% B4 e$ b: g' s( W   Level        Highest        year        area        price        avg_price. Y) Y- Q0 g) A2 p& e  z! j
0        高层                        6        1986        58.23㎡        155万        26618元/平米
  W1 j/ m: l* }, V6 i0 s) `' W1        中层                        20        2020        88㎡        155万        17613元/平米! u' K4 \5 V! C+ d  e% U+ N
2        低层                        28        2010        89.33㎡        365万        40859元/平米. q) H$ B! o. @+ _) Q) n" g4 M
3        低层                        20        2014        82㎡        308万        37560元/平米2 @/ T- Y/ j1 t
4        高层                        1        2015        98㎡        117万        11938元/平米
9 K! t- m- `' g: C  C9 m9 r, R
4 l) Z6 D9 p# P5 v9 A/ f. \8 ]1
- R9 V( n' p, o( j' f6 O# O- D2. E. G" r6 d1 Y% Q% |# n1 R2 y" _9 C
3# r! V0 M$ G1 [, e8 I
4
5 ~8 `* E7 H4 n5 t" l# U59 t! i. M( s& `6 Z# t
6: D2 y& W9 m: p# c
7) f4 L; b, u' N9 \8 a
8
8 ^; x* ~- v( [4 s# J: N+ f; t9% ^! W, [: T8 S
10
# {8 e- M) V" z, r11
# X) d9 l, V. E2 h5 G+ F) S) g124 s! ~5 w6 ^0 R7 ^: H
134 V4 G" q8 L: G* `- J2 l4 v0 ?
14/ D5 W9 O/ n5 K, @* ^
15' u9 Y: j6 S. h
16" }4 e: C) n4 g: J! {
17
& i$ {' B2 x% \) S+ V18
7 S/ e( C8 a' K' \) j! {( w# \' p19% W% o  D# ?: p  y( I
20
. ~/ W( n# |" _* l1 @" r0 C( x# 参考答案2 N/ k7 M. Z& n
s_area = pd.to_numeric(df.area.str[:-1])
% Z( I0 p1 s4 ]/ Rs_price = pd.to_numeric(df.price.str[:-1])
0 p' C0 y" G7 z1 s( c- l" d* Gdf['avg_price'] = ((s_price/s_area)*10000).astype(
, \) j! b$ h1 y) S: {, G                    'int').astype('string') + '元/平米'
( `4 j: M! B  r6 `8 H1 L1 l+ [3 _5 t; I
df.head(3)8 {; z2 d/ }, w7 [7 j. r* t" B
Out[167]:
8 w5 n/ |  X; h+ v+ G- k! c5 _# D& J/ A, r* K   year    area   price   Level Highest  avg_price
* P1 W2 @. e8 G/ n* g8 [; z0  1986  58.23㎡  155万    高层     6          26618元/平米
2 }8 h  I: I/ O; V$ y1 X0 Q1  2020     88㎡  155万    中层     20          17613元/平米1 t2 c. X. _) P: |) Z+ x
2  2010  89.33㎡  365万    低层     28          40859元/平米
. {  \2 H. L& D4 Q$ l% R4 t1# n% Y7 r9 U$ a7 P% p
2
$ i" ^5 f# n& i; ?  u3* B/ p  Y% S' m- _# G- p
4
( F0 \! Z, f* P: q5
0 k% y+ @  o% e! B1 n2 [6
( a3 G. Z9 C1 m; _5 e5 V7- i9 W: i0 |8 G# u5 w) V
8
. _; t: D. V$ j" I0 t! y9
8 z- F: `3 y. S- `4 A. i, _100 O$ K6 {9 R' C5 O
11
" X; o* [; ~( C# G: Y9 [! p" _! Q12
, O3 F3 b! [4 h  i, T5 M( _Ex2:《权力的游戏》剧本数据集
* M( ^+ Q4 O$ g8 V  z现有一份权力的游戏剧本数据集如下:1 r6 D3 p# u7 ?' F7 z

4 g( G, |" Z' @) a) N6 xdf = pd.read_csv('../data/script.csv')- L* a, i/ L5 {/ p
df.head(3)$ |5 j* \6 b7 k: C! D) U
. a7 `8 S; L4 Z1 n% f
Out[115]:
/ f- X, J0 s$ E. B; |0 Q1 e, rOut[117]:
4 n) A! @1 B- w# {3 Q- f  Release Date    Season   Episode      Episode Title          Name                                           Sentence
* L. J% \$ p% y0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...$ l2 F* _7 |( P$ X4 z5 n$ T
1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...
. U3 [% _4 r9 Q2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce , K1 ]7 u2 i* s) Z, [" E) `. t/ Y8 i" T  S
1
( [# M9 y% Z' g20 q8 }8 x( ^7 ]. x. u" w* b
3% {7 u* J* |. J3 B* R% X
48 q( O' i* b6 }$ w; p- z
5+ v3 ?' Q% E3 B4 s# W
6) F, y/ G2 g9 ^
78 M5 q  y! _* |4 B5 i% O; p
8! I( A; s" S1 D6 G) h3 o& D4 W9 y
97 D; M* f0 B7 d1 b* B
计算每一个Episode的台词条数。
/ y4 ?( l5 \( E  {! u5 J# h以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。1 R- j# @" l1 X7 `* J
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。* ], F: Q( L& ]# z7 ]$ k& \
计算每一个Episode的台词条数。
% s8 P4 O  E5 d; r4 U( Y% Cdf.columns =df.columns.str.strip() #  列名中有空格
( v. T, e' v9 fdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()+ M3 q3 |& g; I* d& P" h

3 Q8 f7 Q0 y' Q" c2 K+ C& dseason    Episode  
% E7 H- n1 b5 _3 @0 w2 D9 Z" C! VSeason 7  Episode 5    5054 U+ \3 Q/ }+ v# a" ^! @9 D3 a
Season 3  Episode 2    480& W1 G! F! p- e  g
Season 4  Episode 1    475  ^8 z& N3 v$ @
Season 3  Episode 5    440. g6 O: N4 P, ?6 ^5 T
Season 2  Episode 2    432
. a; N% i9 F' y& W1
: Q" j  G( L# B" t; }0 u( H! r2$ M  W" k0 Y% L) Z# ^
3
+ e- N' e9 [8 U  J3 B4
: i& \# V# M2 U2 e' n4 C. A6 q5
0 X# p, Q0 g" P6 l& U+ n6
3 z' d7 {- b7 T, _' q71 k& f; ~" v7 A# `8 t+ f
8
7 D! S4 v4 z; h* M- x9
4 I2 [! d  s) p3 }, o4 |7 u! s: `以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。/ O/ E- {) s; r/ ]
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数8 O! ?! p. G* q. Z( f$ @* E: g
df['len_words']=df['Sentence'].str.count(r' ')+1
! T- T! K8 d% L6 o9 W$ c3 ]2 V) Wdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()7 \) }5 p& Z) H1 U: S! B
4 b; Q* ~0 g' O  _+ Z
Name
$ p1 J3 G) S: _# c" E- Vmale singer          109.000000
7 |1 ?( b& X7 z3 h  d3 |( E* Eslave owner           77.000000% Z- O( B! B  l) r- D/ T
manderly              62.000000
0 p  Q# ]! q$ Rlollys stokeworth     62.0000007 w) e2 K0 r4 I, u
dothraki matron       56.666667
0 {, I& A  p! k) s8 w0 ^* CName: len_words, dtype: float64
# @8 X- L, A/ e0 T8 a& r17 {; M& m+ g6 t, ?8 v4 w
2
4 x: i2 l6 b) U! V0 M30 B5 u5 Y  |8 b" f" }/ e6 j
47 h" s# K. e  H# V( ?' v3 z  b
5' T3 g- @  f% f2 k$ a: c
6% x$ _; S( V9 N
7: K: K7 G0 C, E7 Q
8! J9 S) F2 k3 c/ f; ]: A! i% f
96 k0 Z( c+ [; P! Y- z  q+ w
10* C+ Y) R$ }! `6 x
113 C9 {, B) g. P8 V' m
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。. F2 a) n4 F$ j, J( t
df['Sentence'].str.count(r'\?') #  计算每人提问数
! ~  p0 N! f: [1 Q# u3 D" s8 Sls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
: o! C' l6 ]( c2 Z; n  \6 kdel ls[23911] # 末行删去. d; p* e, q" K. Y
df['len_questions']=ls; e: {4 ]# g% i& ], |
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
$ j. \1 U3 ?3 X- @+ w
7 }1 V% ^, u; m) t  G0 MName
$ Q$ O& R+ N2 n8 l- ?7 f% otyrion lannister    527/ u0 a. W9 }. {2 ?
jon snow            374% T  z, U% P0 E+ f  y+ m' @
jaime lannister     283) s' D+ R; K, S* L
arya stark          2658 \4 u' G0 y6 f' `* n# r- O2 |
cersei lannister    246( c6 S3 f( b4 Y9 T; J2 O8 U6 k% n
Name: len_questions, dtype: int64. Z" i  {  w0 @& o2 A& F
  V0 c1 T( `" a, F' u. K1 z% V5 L
# 参考答案
9 e% S! d6 m) o" @. [! W8 as = pd.Series(df.Sentence.values, index=df.Name.shift(-1))0 [' Z' B! V2 T, l9 @1 ?" v' d
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
' u. J' c- H- Q7 R7 h4 m& t) T) f! i5 w( {( }
1
' K% n5 u- z" ^21 ?7 b% D! N& Y7 @8 n  I5 S
3
/ X2 X; r, i# |4* e" G7 f1 H$ |7 @/ l
5  g3 ^0 Q. L3 T: k* O% l0 Z
6
& w% X0 c( B* U- n- V0 u73 p9 g! W$ [. }0 X/ l; c2 F
8
1 m0 M/ \, U  \, u+ g9
  P$ l" O. c$ j9 j: V: B& x10
4 O  X# h4 F! F6 \; b& }+ e+ l! E11( |* }8 {% Z5 J8 `, v3 t+ m5 [
12
  ~: a, L* O4 C13, j8 J: U2 q8 q8 ?
14" [3 q: ~8 c4 f) _, q1 X$ u6 o. F
15- t; x7 W9 e7 b
16
2 ?, P8 C3 y7 Q( `17
( p+ P% S( t& N4 c' F/ i第九章 分类数据1 e9 ?# A( M% S- R3 k! m
import numpy as np
# C4 B% A" \% u  ~0 e2 n) Z2 Gimport pandas as pd1 q" a) g% w) Y+ x& f' C
1
2 Z1 j$ n8 U# N: l' x2
, X) I' B# L1 ~% L* k  m3 X9.1 cat对象; L8 @9 W  _" ]& A3 r3 {
9.1.1 cat对象的属性
- {8 i* _" w2 Z) J8 n' R  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
5 f  q" g. \# B/ R5 J: ]7 g% Z- J7 h. @, V, X
df = pd.read_csv('data/learn_pandas.csv',
% I3 W& l( F. [6 s0 V     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
/ I4 M. }) ], Q. n  Ss = df.Grade.astype('category')5 ]! q! y: R+ y! U8 i% T& K
; G3 ~' G; b& \9 u8 p
s.head()
  ~8 M# W7 M7 h: l7 c" }Out[5]: % T5 D) q- R2 d9 z$ q2 c
0     Freshman# v4 M' v1 V8 f6 j* [8 M; Y! ^1 c
1     Freshman
9 M! c6 ^8 o" P, q, Q  C/ P- F2       Senior
! V$ Q% V% U  {  ~6 o1 ]8 {" l# I3    Sophomore
) p2 C7 g4 W; w. ]2 \+ ^4    Sophomore1 b1 r& i& ]& C' s9 c1 L
Name: Grade, dtype: category5 k$ a; N* \# A0 m
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']* V$ A5 a3 {2 X, E8 k' Q3 r/ m
1+ K# ?+ [& j' |! G9 l1 \
2/ Y5 m3 M1 {& B% H$ @
3  `& [( ]) _; m& X+ ]
49 h1 Z" }* u) v# a& q1 O
5
& P, m1 i9 T. N+ j6% Y5 A! N- _# u1 \4 ^1 i6 l; F
7
3 W# t9 [% ~* T4 D: X, g/ \8 h8% r% D1 [2 A* \' _) O* G0 v
9
! O; j3 T# i  j8 K$ V' X10
4 r- U: J* @. X% _4 ?11+ A  I( B7 b: j5 S3 L
12  |5 ^5 U+ l" P+ o! ~5 F
13& w* O/ O; S& N* [
  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
) t8 `6 A- E8 e+ D7 _& C( e7 ^) ?  F( U8 F
s.cat! }& l3 u: O$ \0 H8 k8 p% ^- a
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>" U/ ^: }& @" ^
1, P# `; s3 m- Q+ G% Z: E
2, J% y3 Q* f8 B: K4 n& q! m* e. {
cat的属性:* s- U3 A* s+ I) a2 d

1 G; e# H! w9 o  tcat.categories:查看类别的本身,它以Index类型存储
0 x5 T, v6 t. D/ l8 Q& h$ `cat.ordered:类别是否有序
0 C2 n) F( m- U- o' k, d7 zcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序/ `/ G7 x$ ~) v9 T) `" N4 |
s.cat.categories6 N' e7 [4 N; L  D5 s# h- d
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
% X& z+ R5 b9 r, P: J. \/ d% r3 X. Q9 c
- D+ _' ]( F6 os.cat.ordered
* j% y7 o+ h+ n% H; r+ IOut[8]: False% y: Z$ @  q/ q; F/ t
4 @- o- P; T* t* n  V- Y1 q
s.cat.codes.head(), O& }4 C, z, Y- S
Out[9]:
1 g  ~, K) S: Y9 u0    09 e3 k: n; C. ]% p& U5 Z' V/ @
1    0; D; T% k" d& m" w6 m
2    2
8 u6 E8 ^5 w4 g5 a3    3, v5 v( l: N3 m6 N, R; r
4    3: ^$ a" S6 q# z# C
dtype: int8
8 j( Y8 {. `1 Z1
, Y; T) v& L. ~7 |* j! ]* b2
+ m1 i3 J, {$ I% d3; ?# n# N" `; o) O- C
4
! w4 z6 ^2 t2 X3 I0 p7 q0 f57 [3 u( a' Z% M% b8 A: q
62 ]" b* O6 y1 A+ K/ ~
70 O% b& ^/ A8 t. c+ x
8
4 r1 T2 f+ z3 ^. K$ Y+ g/ z2 X9- V, [# m3 }* M( u
10
, s3 m0 z$ s0 \) H+ O0 D110 N5 X" e, y' V" Z
12
+ O( M7 g0 r) V13) D7 m3 e4 l  s# s1 \% g
146 R8 m. B1 K- y/ S0 j* S' p
9.1.2 类别的增加、删除和修改- u/ I9 u7 I* U+ I# k4 C9 {& D" A8 |- e
  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
6 v$ E; e$ H6 K& w  D: c( [
8 l& @/ m! v& [0 B2 n1 A3 [【NOTE】类别不得直接修改
# ?) r/ z+ J( r3 `" Y* H在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
# R% @& t) F; |& E5 z/ R% d1 O8 O+ ^3 \/ r! a: `9 }; d7 @7 p- s
add_categories:增加类别
( S% c, O3 r/ u/ m" Vs = s.cat.add_categories('Graduate') # 增加一个毕业生类别% y+ A5 U) |$ x# \# Q" H( e* k
s.cat.categories
$ G* t9 R8 W3 W
% F/ y' p& w7 b7 U' fIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
, Z& }2 m. `) I2 t9 p( m' [) W1& ~/ a* e+ `4 H/ v# H" L
2
* }9 ~& @, \& _8 ^/ ?( b& I4 L34 ?/ J7 h4 W5 O# m. M7 S
4: |6 j+ `9 H1 b- w5 G9 H& s2 q- l
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
/ I2 I1 ?, ]* ~1 Us = s.cat.remove_categories('Freshman')
$ J$ _" v! A; q3 Q* L7 i
  v+ t+ S  t$ R9 d$ {* f2 }s.cat.categories
( q/ G- q! j3 H- \1 m# sOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
/ y) f  r; q4 `) G# f. c
8 [+ N4 X0 ?( K5 C6 ks.head()2 J% a, Y* H5 I& V! @
Out[14]:
( P* A9 R1 `7 O1 J0          NaN
6 u* g- I/ Q, v$ a1          NaN
/ K/ x. |; z/ X  i2       Senior7 [8 y, E# E* }, p8 S3 }
3    Sophomore5 q- w5 X7 i2 v% ?
4    Sophomore" `- p# Z" p* Y! N
Name: Grade, dtype: category
* v4 z: I! c/ U( s- f" ZCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
2 v: X; M' o, T' b+ w1
, w+ w# J$ r; ?8 O; U! e2
6 u* i+ f* _' ^5 B& F6 P9 [  j0 f/ k3
+ c' S: @% ~1 m; ^. {8 `. @4
3 h+ I2 K, a$ D5
7 I+ G& U5 {8 A( ]9 I3 I: L5 e6
8 E4 w4 C( U1 }* x2 [! @1 M7
5 w. d* F0 j, u, x) r8
* _# p4 \$ q5 v7 D9
# h- _0 i: e9 k; G10
' S5 h6 i  b# G2 H: I& W6 u. {# n11# J6 E$ b6 E2 M! r( {  I+ @
12( b3 L) J" A6 `
13
0 v$ ?& |& E2 h143 V  m. h; _' c+ r( p
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。1 C' J, t0 Y- b' x" }" ^) R8 J8 _( M
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士4 f, F" S* G% z( K
s.cat.categories% g4 Q8 m. p3 |! h
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')4 g+ G" F2 n' r0 Z' u

# g( ?1 w! L9 G% g0 J3 w/ n9 D' Es.head()
; U! c& ]; \" m, L1 U5 POut[17]: & z( X; V  X1 [* V$ C) p$ H
0          NaN7 [6 I: X& ?' O# N
1          NaN' R+ N' _" ]/ j1 L$ M
2          NaN
8 }( ~3 A3 B# s" c9 ~7 F3    Sophomore- [/ d0 h- u7 ?: j
4    Sophomore
4 E7 ]- I2 J0 B) q4 J. {3 z) nName: Grade, dtype: category  v/ R) ?6 X  `6 E! G
Categories (2, object): ['Sophomore', 'PhD']
. o$ o$ E; l- ~1 D2 D9 {9 M+ P1
& d9 c+ A& U/ }& O2
/ f# U) h( O' X  i$ f$ Y3
  e- j; s; ?; z9 b" C! o4 t7 m+ U44 Q3 |- J) @3 n
5
9 G8 f: o) h- ~, z6
! ?6 f" N& Q! M7 N' q7* Y7 H' F) q# V. N
8
& B: F) m5 }* R' i  v" Z9
0 r: ?9 N+ W% N/ d5 s7 L. O/ v10
3 d: b: K4 Y. n9 v3 V" ?* ~115 @1 D, d5 A9 v
124 n9 m: a0 Q# ^# l
13
, V3 E7 m5 P' k) g1 nremove_unused_categories:删除未出现在序列中的类别5 S* _2 a& x$ ~2 E
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别: z% r! ]2 ]* A: i& `7 o3 x
s.cat.categories" g' K  I* {# w, }. F- {7 d
5 X/ G2 z1 k" |7 q% a
Index(['Sophomore'], dtype='object')
7 p' R5 J+ H2 ]1
+ d. J6 s+ d2 R  Z2. ~2 r9 U/ [: {- Y( L; s1 {
31 ^5 D) c' [7 |
4" Q8 Z: G( m# N+ L! N
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
% a! |! N/ |$ V* v1 m/ Ks = s.cat.rename_categories({'Sophomore':'本科二年级学生'}): [% ^# h, y+ u. u$ Z9 u
s.head()" c5 V0 y, U3 |, r6 h; K

6 w; ?  o# I, ?/ x0        NaN6 B( c; N9 a8 h- v' f0 p9 R2 r
1        NaN
& J0 r( Q! _+ L' C3 z8 `- U2        NaN
- ~) l. ~' ]5 [) l( Z3    本科二年级学生
5 p$ @' e# p9 [3 E4    本科二年级学生# {; k6 k9 X/ Z2 O7 H) Q7 Q8 k
Name: Grade, dtype: category8 B% C8 Z6 V7 p" y/ p* N
Categories (1, object): ['本科二年级学生']' A6 N, ]& w5 d% n; D5 K1 c
11 m$ Y7 d! S' P5 z  `( N
2. i! r% I& o+ |% Z" t4 F
3
6 w3 f5 M  `- C7 P+ q* j4 o1 B5 j6 b4
% b; t8 f( L4 ?( F* N5 F  A5' A( J4 C; D8 v8 P4 z
6
! m/ M( t! U$ A& H- G+ P7* X! k+ t( d& }% g0 B* x
8
$ @0 |# `6 r- p! u' _& u9/ q: E" i3 i; N
10
" q$ N+ w( ?/ s% ^& j9.2 有序分类4 c" c2 z' h* D% p7 Y
9.2.1 序的建立
" c+ W8 C7 _' _4 }  v/ ^  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
: l( j6 n/ q; @1 r! \6 ?8 E6 j3 e0 D. W- f/ ~/ {+ L; S! {
s = df.Grade.astype('category')
5 H; Y2 _1 Y) b1 i- F) `  Js = s.cat.reorder_categories(['Freshman', 'Sophomore',: s! e4 D& V5 @
                              'Junior', 'Senior'],ordered=True)) Y" l9 A  u5 M5 B8 `/ e
s.head()
' }2 G" b6 b: K' I. [Out[24]: - G8 ]. H. H. R0 C1 e
0     Freshman
3 S1 e- a  O: r& u/ b1     Freshman
. ]: n+ k4 s- u3 h  Y7 x2       Senior' D: X9 G* y2 q  M( _
3    Sophomore
4 ^1 L4 @5 y4 Z! V7 n0 s0 H4    Sophomore
6 X6 o5 O& e; F5 s" P, ]! NName: Grade, dtype: category1 G7 R6 H7 U: s8 M* k$ [, o/ G
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
, m, ]/ I) Z0 `5 I$ k+ i
. c% T  m8 w" Q6 \s.cat.as_unordered().head(), L1 H. j( y, _- |, F4 \. o
Out[25]: 8 H3 u, r& ?) C" T
0     Freshman
% E6 Q  `8 D8 G9 E1     Freshman$ Q( P. d' d% D3 N2 p
2       Senior
6 Q6 i0 }! J; _' @% w3    Sophomore
' v) S& m3 m0 f  v4    Sophomore; U: B8 h: C( r1 y) u
Name: Grade, dtype: category
3 ~8 ^; S' V8 ~* jCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']5 A6 f: R0 R: M/ E. V; U; L8 b

, Y! T  s5 l$ o# F- y1
+ J: V3 {* s  L0 X3 L8 G/ W- l6 e2) M9 v' X* a( L1 d; b( j2 B
3
6 {# U9 v- z4 k9 e* I! y( J1 Y4
( L7 D$ T9 U; [' i+ i* n0 l59 m0 f" Z, t6 Q. S, d' K
6% `$ y$ s, L, Q: J7 E2 C% D/ S
7
/ t; D2 L7 r3 k0 W, y: c! v8
6 Z6 U( q8 A9 \; \$ w9 z9
$ D& F  [3 H! M+ i8 N10
6 K( H6 a% w& z3 M" A: E114 H  }+ r) |& l5 _0 ~0 S
12, V; ]: z/ S2 o
13" `8 D0 @+ L4 k
14! l3 F: ^) ^9 H4 d4 y
15
9 n/ M0 l" l& Z8 }3 N, k$ X16
2 ~% M7 f6 O# t5 x2 y9 n9 p17
- Z0 E1 }6 {+ L; I- l18# S" T( P+ R/ {( r9 G0 O8 z
19  i9 E5 Q7 }% |0 m, G
20
; g) ]' J* f( U. I/ q* r21
/ M6 P" e/ Q( j) n22
9 t$ M# \) k7 x1 X! R  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。' j9 m' G5 d; W! g% [
3 }: L) N0 q' I0 e
9.2.2 排序和比较
5 T$ b1 r6 B5 o" B, Z. S1 {在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
9 k9 w7 L' U" w5 v% Q7 Z3 t  d3 G6 C1 j6 ]1 o( i
  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
- B# N6 F% j+ \" k: C& Z! t* V* D& f- Q- x9 W
df.Grade = df.Grade.astype('category')) w' t2 p+ R- [4 S7 k9 D/ Y# m
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)) `) E" g4 E) y0 g5 b  Z# p/ v
df.sort_values('Grade').head() # 值排序! D' C# o# D# h
Out[28]:
& X" z1 F# l) z        Grade           Name  Gender  Height  Weight. [4 S9 z( u$ L5 ^. h; O
0    Freshman   Gaopeng Yang  Female   158.9    46.0( m6 \) h) a* }  k! _2 c
105  Freshman      Qiang Shi  Female   164.5    52.0
& L8 \( t2 F4 r$ Q8 B96   Freshman  Changmei Feng  Female   163.8    56.0
/ y) j& Q+ p5 ]0 F$ U* P88   Freshman   Xiaopeng Han  Female   164.1    53.0! I- B4 _) ~; D0 M9 s3 {- H
81   Freshman    Yanli Zhang  Female   165.1    52.0$ X' Q4 W& U8 M2 v; P

- S5 n% j7 v& z" ~6 @* hdf.set_index('Grade').sort_index().head() # 索引排序0 O4 a8 ~8 ~: M6 V
Out[29]:
: |: P' D4 J2 n0 `/ M+ E. P9 ~6 v                   Name  Gender  Height  Weight0 N# t/ n& Z0 m4 {- R
Grade                                          
1 S) E. a/ p: l1 ^! @Freshman   Gaopeng Yang  Female   158.9    46.0
. S! t! `+ z2 N, [. I$ y8 DFreshman      Qiang Shi  Female   164.5    52.0% {" s2 H2 a! O) C2 N
Freshman  Changmei Feng  Female   163.8    56.00 [0 _) b9 |- B- |; A2 B& I
Freshman   Xiaopeng Han  Female   164.1    53.0
7 Z# a9 y; C: y- j) r2 U6 S2 y( BFreshman    Yanli Zhang  Female   165.1    52.0$ B6 C' `# B+ h

7 e% S. n6 G7 J: |/ n1 K1
( B7 @$ {9 L: r! `3 t* @4 I2
- i# [& v1 a3 a; Z3 w* n3
" _: Q! C4 K) R: y4
8 M: a$ K# m8 @9 }3 |7 {56 R! A; [% S: g. b0 ~+ H5 ]
6
! \, k5 O9 ~; J, A8 r! |* k7( T( F- {0 n% V
8
( a  \% |- F. U! {. @( K2 G93 ^) A6 q3 _" T% Z9 g  O" D. Y! _
105 F" |1 W: i! Z  t5 P  s* S* v6 e
11
  Z) V$ m( \8 s' R120 F0 d" Z+ c/ K
137 h3 F* c9 s% z4 y
149 l5 Y, g% g+ V+ O5 {0 w5 E  @. F
15
2 d+ `4 w, P8 ^; M16
! P$ ?+ j+ y# C17
8 X- r" q, I5 D9 D! @18
5 C% H& e# M  `! t) p19, Z' O8 j) O% ?6 f& K$ b% ]5 c2 C
20
; `, @7 N. E8 e) |& {" q* U! H$ |& j  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:- n& t+ Z7 V2 q+ `9 ?5 M' |/ R& ]

+ Y$ F  p5 ^" V==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)4 m4 ]' |( m% n2 L" k/ r8 c
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
+ \3 ]3 \3 H6 ^+ e2 yres1 = df.Grade == 'Sophomore'2 f1 h; w* P# Z: m8 p

% t( V# ?0 p9 t( h! }* a5 nres1.head()
# h8 ~7 P( T, I: @+ ^( MOut[31]:
4 I" |; y( W2 y2 C5 s0    False. l# A! S" ]7 C; b/ [. U8 J) U
1    False! O  z: p$ m3 e1 Q% h
2    False
& y0 x9 \. M$ T3     True  S) |& Y% U" q
4     True; d) G& J/ @' d" V0 X
Name: Grade, dtype: bool
& r7 H; |. Q0 B: c. ~4 R, d+ p2 B1 [* W; x1 l& l
res2 = df.Grade == ['PhD']*df.shape[0]
8 p! I5 J+ Y$ m' ^1 n+ j6 Q
6 S+ e$ i' m- A- jres2.head()
1 w  C2 D; n5 Q4 {2 k( a  U8 T7 kOut[33]: , |* y7 l4 A4 s6 P
0    False, G. |/ h0 J# w5 r8 T
1    False
3 w3 Q) E4 y# X2    False
& s* T. Z" s$ h6 P9 {3    False
, ~$ [; q5 x; }4    False5 i) g0 \: R; B
Name: Grade, dtype: bool
1 Q! B' V! M* a0 V6 [* d2 X* R' _5 N8 Z& X' [* T
res3 = df.Grade <= 'Sophomore'
  c) u. G/ `7 p% X  q7 ^/ f' R, b8 w7 n' u
res3.head(); O: x8 q  P( L( u
Out[35]:
/ h0 ~* B9 F  j. S' a7 ^3 X0     True4 q  i) Q* J8 Y, K' u
1     True
6 t: Q. g+ H9 \& U1 O1 ?4 }: {2    False8 t, l; K. c. ?" G2 N
3     True
; l7 S$ J6 i$ h4     True
$ b$ E% d3 K; |  z! f  zName: Grade, dtype: bool
% J4 }7 O* v' P3 I
/ o) M, a1 j5 Z) D0 o4 m8 Y. T+ O# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
3 @" h9 t: \" ?# E  v+ @res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 0 _0 A* X7 ^6 L0 y8 N+ w3 K  K

" h& ~2 d' k% b9 l* {res4.head()
: f. P" h  T! b1 T3 F* DOut[37]: 1 Q% P4 t/ T/ C1 N" e. G. m4 I0 k
0     True/ B0 l: Y* ~- K
1     True
% a$ P0 h6 q. M$ |7 c2    False+ u6 b5 n4 D9 J( O' ]: h
3     True  \- U5 s+ `- N9 i( p
4     True& O4 U9 T" d5 e8 d3 @# B7 r
Name: Grade, dtype: bool7 Y- F* w% n+ b, {$ @
$ a6 |8 U4 ?$ y2 x0 ~9 w
12 y: o5 N) j% r9 p% `9 E% n4 T( a
2
" T. \( Q. [9 O1 b3) ?+ }: _# j6 Z$ l, {8 A) R
4
( Q; P9 c& ?- e$ j50 \; ~  `# ?9 q, x; T' d
6! {) U+ ]# c/ x" a" K' [$ Q! @
7" m. l+ `3 h. C3 _" ^
8
* X; R% V3 v* Q0 c5 l98 N# e) |0 O3 `* `
10* q- ~6 U5 M! u* l
112 Z- W5 V6 z( ^9 ]
121 q) ?- G. q5 l4 s  N. h2 F) U: J
13
3 Y: |7 `  Z* ~: l+ n) c6 S14! N+ q/ s0 ~# [5 H' H
15* Z6 P7 a$ r( L; z$ D- V! g$ o% S
16
/ x6 Y0 J$ A0 i1 F. D% ?8 {# {17
- m) U" o8 n" ~( r18
4 \. {$ C) m/ N0 Q/ I0 w198 ^3 E3 J1 J% R$ r5 E% `( `
20
) I4 L+ R3 E& l) x7 |21+ P  `4 U3 D9 L& }2 B
22
0 K1 E' G- r& s; y  h" K/ w23
8 q: e2 I0 ?( g# C( o24
3 s9 b, c- G3 ~, y% P% w( e25$ b' X5 O9 ]: S& S3 K3 T
26
% q5 Y$ \: ^$ [3 J2 u$ W) n+ g  A5 r27
4 i, J' E! X/ W, ~28+ Q% j' L" j* ]1 H
29
5 n/ R/ |$ k. p# R* @3 f30$ i: _7 _5 S6 N( R  r  c8 |% f
311 D+ x& A4 q2 b4 D5 w
32$ I' y" j; t/ w1 @0 Y1 }* i
33$ w- [% E, G# f+ D* q
34
+ J* {0 R- A4 B/ o" ~! f) X35
; v/ [! Z  A9 M6 ]. X1 d36
9 d# G# I  {' A* L3 J. m37
) H% \! n% S7 I, h3 F4 ?  E38
* Q3 ]/ H: D0 f39. j$ a& c" L6 V! ^
40
2 K0 A& V: M0 p4 G5 a$ c! k414 i  l( I* r+ b6 D  B2 R1 r
42( w$ [: D: r  p5 P& ]3 A. c4 L% P4 L  U) {
43
/ G4 V+ x, k* f44  t% X: O# A; c! K* q
9.3 区间类别% ~  [3 J# l3 n" U+ U. R$ z
9.3.1 利用cut和qcut进行区间构造) ]5 i2 l" g9 Z. p
  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
0 k+ s$ r. s' J7 U: V
4 U$ I( j* N* _3 k& P0 {0 Scut函数常用参数有:
) T* d" c& L  h5 |$ T3 wbins:最重要的参数。
4 [9 i5 a, o' e3 \- L/ V如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
1 G+ H' ], w) {0 Z* n也可以传入列表,表示按指定区间分割点分割。
& t5 R: i/ V) u, q( ]' p# C  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。, z0 i. Y+ x! v! U& R& F9 o
  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
# }) P; V& P" n' _- a, U2 T
3 u1 m2 G9 [% A# E8 p4 G  ls = pd.Series([1,2])
& U4 k8 \6 {3 w# bin传入整数
9 o' l5 E% \3 |: u
1 p0 ?- v# ^0 E: N7 Xpd.cut(s, bins=2)! {* F# d) k1 O  q, M# [* |7 n
Out[39]:
: n- ?5 x. x; Z/ ?2 y# t6 \# O0    (0.999, 1.5]* [+ s4 n5 ^/ H0 I
1      (1.5, 2.0]- \" A/ x8 M9 v! s
dtype: category
. Q# f  v' \5 q: h# HCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
5 l5 V! E' C0 A3 Z  H8 z' K9 c) U
pd.cut(s, bins=2, right=False)
/ K1 G/ b' c9 @. LOut[40]:
1 [5 p; `5 g1 {; n2 F0      [1.0, 1.5)/ O( o/ t( y9 l, _- M
1    [1.5, 2.001)
9 [- X( x1 e$ edtype: category
3 F) ^) @' A" B7 {) mCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
, j' }7 G: \" w/ A7 h8 H
$ t& v7 K- m1 e& l
- B) l5 K$ G: P9 O3 I  l# bin传入分割点列表(使用`np.infty`可以表示无穷大):
2 U3 P5 O( V0 V, Mpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
; {0 C0 |# a. ~) Q# C) a6 ^- _Out[41]:
# ^) V- I1 \# [* v: J  O0    (-inf, 1.2]
' V1 b; u8 @4 s* r: g, Z; H( s1     (1.8, 2.2]3 H2 {$ K. F# z+ \
dtype: category/ Y& A2 C6 L& h+ e0 |
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
0 Z' I' d5 [4 G1 l. l- ~6 y8 r) s% M9 J/ D7 A0 e
1
: u/ f6 o6 O- C  P* n29 o6 h. d. N7 X4 O7 k
33 @8 @) y& l" k; s7 ?( ^
4
4 Z+ p$ X9 [0 z6 L5+ u( P5 a  U3 S# G* g0 ~
6
) |/ x+ Q4 O- V4 Q- |9 `7
4 N+ B( D! b- H* k/ b" c5 M8! w+ L4 [: T4 C( V; }) B! v5 }# Q
9
3 z7 F' [. x/ B3 ]/ F3 n9 b2 n102 q/ u3 `: v% z. ~! m# y9 b
11
; ]. D9 V& A" _& ~12
4 h+ ?- ^9 X/ m9 E9 x9 S13
, e& V+ z8 Q) s0 i5 f& }14
: z- u6 j) d5 m- ]! P; j8 O! l6 q15
" R3 U6 a5 I3 l16# ^' c9 a7 k( l" f; B$ D9 ?
17! W! V7 f1 \, m* D2 A7 M" y
18, Y$ W6 Y8 W; c# v" \  L
19
4 p  |: J1 z8 X& b20
1 f: G+ D+ g7 X. w: y4 l7 N21
" J4 q% Q5 {( g22/ H1 R* M5 T0 R  g% r! @3 P
235 w  R+ q3 D& J1 e& h- w! C" p
24$ n+ h% H* h* @. x
25, M$ J9 b4 O8 @" T0 S. G2 ~
labels:区间的名字
9 o5 }. a6 F$ y, tretbins:是否返回分割点(默认不返回)
" F7 h7 o+ Q: u默认retbins=Flase时,返回每个元素所属区间的列表, u5 T, q) k- S1 D
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值9 W5 l; C) h  e! D: ?: [  l
9 a3 U5 o) ?. A# C$ K  _0 E- i' l
s = df.Weight+ T% i$ h# X& x9 R( \
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
  O/ @1 T2 }7 u8 o1 J, s4 }# sres[0][:2]) G1 Y. F& m+ G% ~  g
* b5 P  W# G$ f) A  U; X
Out[44]:
9 x( Z5 H* T& V0    small
4 g; A4 @" b  ~; l8 D1      big
! e, J. T9 g$ tdtype: category7 _4 ]9 a4 }6 B4 X. p- v
Categories (2, object): ['small' < 'big']  t2 y5 U' Q4 C% L; A; ^
; f# t$ h  Z' x4 A  r4 l* w6 T
res[1] # 该元素为返回的分割点
2 ^% L1 `# u' c# r" ?: P, xOut[45]: array([0.999, 1.5  , 2.   ])( r8 _8 X# p9 k2 W: L" U
1
* h; }/ o3 I( \% v- g$ O2" b( ?3 x* r2 @) L% ]3 @
3- \" u' ^. F' c$ z9 S
4
7 @- A  z4 @, v' m5
0 z3 G  j( Z" b1 @6 b% P9 D& j# N6
1 y8 J5 F$ l: K7, V1 O  q& _5 i. y
8/ b1 e3 a$ B4 d
9- U0 U3 r9 Q5 e7 r( u, J
10
2 a4 l, l/ q2 h11, V" u6 x- r# ]0 b. z" b
12
3 C2 j3 ]& A( c% t8 ~8 Kqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
4 G5 k/ P: T+ B6 Z+ c1 }# _q为整数n时,指按照n等分位数把数据分箱9 t6 X- `: Y; d6 Y6 [! H
q为浮点列表时,表示相应的分位数分割点。
* s: W0 V, Y# a- y2 U9 Ms = df.Weight6 J9 i/ x. I9 I0 u4 D! f
' l3 U; K) W8 c+ |. Q
pd.qcut(s, q=3).head()
* \) e  ^* {  u1 ~9 V3 w  M& \5 d6 yOut[47]:
+ q. V  o# ?* r, G/ P2 h0    (33.999, 48.0]/ N7 Z! l# t! w7 v" F
1      (55.0, 89.0]* W. R/ U7 s- e/ A
2      (55.0, 89.0]
- J1 q% ?- K  h1 |3    (33.999, 48.0]
  d( E. M) Q" q/ |4      (55.0, 89.0]
/ P* n% o  \) x' m* n! h% _Name: Weight, dtype: category) e% Y5 k' I- r7 Q" Z$ d
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
+ [3 X  s. n; M) ?1 A, `  s9 X" h% O; H
pd.qcut(s, q=[0,0.2,0.8,1]).head()
5 f4 C" R, R( L+ XOut[48]: . A2 g, y8 k- F* Y
0      (44.0, 69.4]
9 I4 I3 q* m! O: X( K1      (69.4, 89.0]# u# t7 k4 O& N2 E/ i! m' E0 o9 }
2      (69.4, 89.0]  K+ v7 x- K2 ^4 [& q
3    (33.999, 44.0]
) x# A4 b% a+ t) Y/ K# R4      (69.4, 89.0]
5 r! d6 Z# i  SName: Weight, dtype: category4 a7 b$ Z: ^: S- t9 o; g6 r
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]9 n3 w, ?- z" v3 U

0 E9 X0 c( t; b* D  E# W16 w- D. O) j* w4 p8 d
2
2 @' P1 [" R5 n. M* B3* L0 x2 Q( `, ~5 Y1 c$ e
4
4 D( \* ?0 A$ _& L. E  i9 H5- H7 ^9 z& N; l
6
" U* M2 ?$ }, [4 \- U78 }% t+ `' B: U
8% X' N# I0 }* s2 t
9
- g: E. G2 o4 ^* F, z1 P& d10
/ F  h& L0 _, s11
( N  M1 V  g/ ^) s9 q# _$ n: {12! f" M- ?) x2 H6 X
13
2 z. H0 G8 _- I. t14
2 t$ _- m) \4 T9 O15, Y" `4 {3 {) s4 K$ d
160 s- K) m1 s; i8 ?$ h& ]: ]" U9 }: k
17
5 n, Q' U, e. O+ L# S9 r) p184 Y& _6 s, F/ I; N1 M, W8 k7 K
19
: M/ h$ w. b$ n4 t3 h20" y- b/ M8 [. B: ]7 o4 a
21
4 \, J; Y1 @7 A2 }. C; p9.3.2 一般区间的构造
) U9 ?5 J8 P2 u, f  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
( g7 c3 z; r* _; }- z! |' L$ D& `4 Z: O6 e
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
9 \  n/ g! H) m  Xmy_interval = pd.Interval(0, 1, 'right')
) M, J  R8 K) @; q9 A
' \& q1 ^% h; I" N' c8 \my_interval% S; b0 |: t; d0 r' G  B
Out[50]: Interval(0, 1, closed='right'); u6 Q4 l6 h7 S1 ~0 w& R4 g! E  s
1
. G- f  Z' f/ ]2
, X/ C( \0 O( P7 a3 ]9 b38 h8 z. Q$ N6 w; S$ l$ F
4
. v$ ^8 Z0 v) _+ _4 X0 Y: N! }区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。- ]* y0 S4 [, [8 O
使用in可以判断元素是否属于区间
  h7 d  y. h& W7 `% s: J用overlaps可以判断两个区间是否有交集:* t4 f. O  E1 b
0.5 in my_interval8 p+ W2 p$ n" j8 b; H3 o6 N
( m" p; b7 |/ b( g' f$ C
True# E- D2 u' A7 B# D
1
1 F5 u# y, A' r& Q# U4 k8 h21 h* b* Z! H: H
3
1 U$ J9 e; t6 Smy_interval_2 = pd.Interval(0.5, 1.5, 'left')6 ~8 R) J- \+ C4 X/ t
my_interval.overlaps(my_interval_2)
4 n* R( `' k% Q( F* H/ x" ^. ~, R3 ^' F1 \) n6 B) Y
True
# ?* H6 `5 I  a9 h: W, l) F/ M1( ]) {/ j; ?; v& ^8 B: X
2
8 L2 ^0 \) ~! b9 K, V2 b8 ^, M3
! k6 T5 _/ J9 t- [1 n5 F48 R+ p+ N! @+ f. z* t5 c
  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
8 L. T1 H- t& q. Y* O- a2 Q* z
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:& {! ]$ O+ I4 D1 }# ]
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
) d3 ~4 J0 }5 L; m) `6 O
9 j: T6 B8 g% sIntervalIndex([[1, 3], [3, 6], [6, 10]],; c" F& h( A. `+ q
               closed='both',: D! s" b  B; b3 j4 ~. X( x
               dtype='interval[int64]')8 p3 Q' A* n$ y3 }2 X# [' w
1
8 a$ w3 I9 P! N8 t6 Z; u2( o6 R/ z- s  N6 x# }5 C
32 w  I+ j$ v% Y; E
44 i! e6 b+ U9 D; F. a! d
55 t. G- p0 ?6 H* j7 R
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:2 y* U& ~; }% I  g3 B" ?9 M
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')- ?$ K; S0 H( |5 I. x, i( c! i8 H4 W

4 o! O5 L: A1 J& B6 y5 {. ]! |1 i% \: uIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
& i3 w$ h7 V! e: `                  closed='neither',
: D3 `) e: U4 m) H                  dtype='interval[int64]')! P: H. ?2 ]! V* ?! N
11 g) E' P- Z: @) s" @
2
9 P2 O6 Q# u+ i, L3
2 x# b$ {2 [- |47 v# \3 ?4 d3 }* V4 [2 Z
5
+ V5 ~  ?8 H/ Qfrom_tuples:传入起点和终点元组构成的列表:
7 Z, x. b* X; H0 Q& \. n( Opd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')% I, a+ p- A+ C( J3 f3 M
0 M' b2 w# V3 B6 R, J. i% p
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],* h- U8 d! E/ q6 r
              closed='neither',
) z% l! p" K0 p! c9 Q! c2 y              dtype='interval[int64]')
2 x' `7 ^' H, ~3 l0 @# ]7 d1
& s0 E4 j, N& u; p) j, H( g2. t, n6 L% ]' o( ]3 J( w# M
3% C5 ^; v+ v3 [# M; ^3 b
4! v" y0 @3 ^5 `4 s% k" q- {
5
9 \. v5 E2 f: }3 Qinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
7 z7 }8 N$ k# p! {% J. N, t- ^5 W3 Gpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
) _/ W+ O1 x9 d; A$ s3 H$ JOut[57]:
- N2 f3 J8 N' \9 bIntervalIndex([(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]],
" x1 i/ _, G% C' }2 {              closed='right',
) |6 m$ A9 l% ]% m, T' J& b8 i              dtype='interval[float64]')
7 E5 l: v: T( I6 _3 x; n8 C* [% [) `' B$ N+ e
pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
# ]  x  t" Z6 `0 Y# o( bOut[58]: 0 _: x3 n5 O0 Z! J4 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]],8 Q: K% v+ v" i7 D
              closed='right',
0 X  w2 W7 g7 I& s6 u* I: Z              dtype='interval[float64]')# O7 h* [* z) N
1
* s  v( ?$ X+ h2! j& C& C4 i% J, b
38 y4 J" h( Z# Q" ^9 L1 {1 e) l+ l, f2 U
49 y, t; y7 T9 N& W
5' K- ?' u9 N4 k4 y! ?
6
! o2 A- e3 O) x$ M2 m; q76 m% W+ @  o% @3 z. J/ E
82 j1 z0 ]' U: _) L
9
6 g, x% |; y) G; c3 A10
# Q) o3 T0 t7 M. t3 }; \11
8 B7 X) W9 u. L5 A5 U/ @【练一练】
' f9 g. K$ E9 S( O0 E2 k4 Y  |  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。& l$ ?: K5 |, p8 r

* ^4 P' b. L6 o* L8 s& p# I* W  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。0 X" J- Z8 i& h) K" ~

: O% V& |5 H4 m2 |: ]: {my_interval
" b) o/ M  y$ O. A3 qOut[59]: Interval(0, 1, closed='right')
* r; F, P" [* p9 P( J$ a! {3 V7 }: V4 K9 m$ {+ k7 Q  w
my_interval_22 F: S7 Q6 s' M! j, y1 S- W+ T9 p
Out[60]: Interval(0.5, 1.5, closed='left')" g' f5 P0 o1 C! ~3 h
7 a% g$ V7 l2 j
pd.IntervalIndex([my_interval, my_interval_2], closed='left')
) Y5 x: c7 p* \( G. ^7 j1 e) JOut[61]:
4 V0 V% [# U8 q: AIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
9 e  a- X5 l$ k& Z( n* o              closed='left',9 n  E' Y- C0 n2 i
              dtype='interval[float64]')4 V3 d' `1 l) b4 v# ]
1- d9 H4 m2 s8 v; b( G6 p0 Z
2
' q# h7 G  j+ v' ^9 {3
0 i  F3 Y$ E7 T5 y. R2 h4
* T# l; z# u0 D' V( p5
( q0 C5 e5 y$ V8 o4 c& D6# O2 E2 F# Y0 v  n2 ~7 _# b2 U2 J
7
( v4 Q) c6 x3 z2 s8# g0 [% m# L: G! T
9
6 B$ p% Y8 u- \; X; j1 ^10
* J/ q7 w& N$ O: t' ^. e" ?* I11& m* O  l, O) C( P" W
9.3.3 区间的属性与方法
0 \: ?( c9 ]3 X+ ]1 o  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:* @5 s7 {0 F. D+ H8 N' r2 k& b

9 F$ k$ \# W: }9 x0 us=df.Weight! \; M& X. T' q0 O# N$ K: n
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示/ `; ]  X% Q% e) L' g( V
id_interval[:3]
" Q6 U" z7 X& ~" L8 v# P1 U$ _
" T/ [+ P, e9 h* T- g; v6 vIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],0 O7 Y1 _- L) v) R, ?& i7 M2 P
                 closed='right',: e0 Z4 g8 E" V# c0 S' \- `9 M, b
                 name='Weight',
6 ]" P0 M9 ?$ K, O9 \3 s: n9 d* R                 dtype='interval[float64]')
" @( S! y/ ~0 b0 F& [/ n: ?7 ]1
  T. o6 ^9 V; d8 N/ q27 W# P! t- A  x# U& c
3
$ \; T: U$ H: s) N* _% I/ y4
+ n/ Z! X( w3 o! K) }8 z; }$ c5
' B+ E# G$ ^! h: G) C: p6
. X! \3 ~5 i+ t4 m( S& K8 W7
; F  T. W. H* ~8
$ h  ?* L* U) \, u+ r9 A与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
7 j# ]) j( ]  j* W" f2 Fid_demo = id_interval[:5] # 选出前5个展示! u& t. `8 ?) t5 @  f8 q4 ~, H6 n5 @
# `8 K/ w! D: o" r6 c1 L
id_demo
) S* a% U$ M3 k* ZOut[64]: 7 a, H+ h2 K( T) s" m5 I
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],/ i+ c' J0 T4 o6 z( t) ?& o1 x
              closed='right',
+ ^6 n: S6 ]& j0 U& s: s" I              name='Weight',
* v" S! K+ [; q8 T7 ~: w$ F3 c              dtype='interval[float64]')* H* {. ~% Z/ ~" k& A

2 H5 g: X8 f. r: mid_demo.left # 获取这五个区间的左端点
' Q' X& `2 E) j( W4 t, z8 s  ^Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')4 c& V! R$ {) Z8 F8 J# A) D7 h
: E6 \. u% q, Q6 `
id_demo.right # 获取这五个区间的右端点3 _6 i9 n: T: L& h! D9 E8 e* Q
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64'), c/ f$ n0 V$ `% e6 i

( j5 u+ L9 U1 {9 @& Q6 kid_demo.mid* ?5 m4 l0 E5 u! j% q9 Z  F! s
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
6 _6 D6 K: ~0 W$ m5 s
1 r6 w& K0 u7 b* \( I3 Did_demo.length
2 p0 k3 X, Z$ _$ w3 J6 mOut[68]:
( ?! Z' `7 O3 X5 rFloat64Index([18.387999999999998, 18.334000000000003, 18.333,5 [7 _% [7 X; z/ [
              18.387999999999998, 18.333],7 ?; y, T, i; N) R. x$ T4 ~" W
             dtype='float64')9 W9 t  \5 o/ y: S8 Q* Q( [

" q4 O4 O. ?' @$ _15 I! Y; A( Z, @
2: \% r6 |1 F" k! x- Q
3
# i- V6 o+ m+ e4
- S" \$ K8 d6 M1 O9 J) Q8 l0 e58 ?/ K" D9 N+ F' O
6
- a/ c6 w% e+ L) k7
5 u- [4 _& e; \0 f8 i0 @/ \% z8
1 T# \3 G' p4 w9
2 d# o. V1 S6 h1 X10
) \: ^9 t: H1 B/ @11. E& Z) Y- t: Y3 G: G5 g. l
12( Y! u! o' V/ v5 V  ^
13
% G: Z6 P: }/ D; |- T$ A9 n14/ @1 Q4 L) ^7 U, Z+ E
15
4 \* J" l; E: y166 C5 ]2 \$ W! @
17) \) t; `8 G/ T( y
18
7 k3 U1 q4 |; v- O0 I5 {19) k4 y$ G, W' J- C2 n4 J9 t' i
209 G5 T, g& J- x! L
21
; N- x3 C8 A2 S. n$ p, o/ L229 N9 P" g% _, f$ K7 d0 M, \" _
23
; E5 v( R: x  z; B5 J. XIntervalIndex还有两个常用方法:6 Q/ @( S( g  @
contains:逐个判断每个区间是否包含某元素# n$ u  k& j, l' ~* [
overlaps:是否和一个pd.Interval对象有交集。
) d/ z" D6 Z5 Pid_demo.contains(50)
' l6 f3 T1 Q* @' Y" vOut[69]: array([ True, False, False,  True, False])
; [0 [- f8 e4 N6 W0 g4 @, n6 |2 K# g: p
id_demo.overlaps(pd.Interval(40,60))
  \6 W  b- Z) E# E! F, ROut[70]: array([ True,  True, False,  True, False])
- g1 ?7 e; `3 V  {. y. S) P1
+ x# N3 o0 d# D% P- n. f2& I0 L/ _) ]$ E# G
3
; y9 O# x8 E& Z  U" ]- T48 n7 O- B  ?1 p; `
5
8 B. e4 F5 J/ ?, i6 U- @9.4 练习
- E6 D& w, E6 ~9 GEx1: 统计未出现的类别$ A5 g- ]% w  U
  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:; n! I. V: |/ n
; ]0 E4 ^$ U$ t8 V& @! X3 Z
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
# U: O  V9 R% h: o; zpd.crosstab(df.A, df.B)
7 H8 R' }, s* ]% G  u; f* _/ U' P/ k+ R' l: B
Out[72]:
7 B0 j0 H7 F4 b4 J( L, W+ tB  cat  dog' G: p& i* f4 E/ v5 E9 D( \1 u% c
A         
6 \9 x, p# I. x5 W) ua    2    06 W' n1 K$ M; `* M# }# P! C
b    1    08 _1 ?+ l* m) ]7 \9 q8 \
c    0    12 i$ M3 o" H7 n; A' a# l' S
1
! X/ F2 c6 O9 p# ^# G" Z: w2) {" W+ ?/ O: E0 `, Y5 k( E( J8 `1 G- J
3% U" m3 M/ ~3 B6 c% D
4
  ^) h! n: m- n" y51 b6 M7 j' }) t! `1 g* \
6
% y& ?3 B' y. Z4 m5 X: [76 O1 d" A( ?; j* W/ V9 [
8
  v% E( [4 l* Z- k9$ U$ z! P0 f  r2 z5 ?( }
  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:3 f  F( H5 s& ]4 d' n1 D
! B6 h; K  s  Q3 w' d* x5 N
df.B = df.B.astype('category').cat.add_categories('sheep')# Y! [- D, R' w1 E: @+ G
pd.crosstab(df.A, df.B, dropna=False). O. a) Z: b  g2 B; O  P0 N! z
. W: h; S- K/ k* |
Out[74]:
6 ^& V; O) \6 Z& eB  cat  dog  sheep5 S0 ]$ K! t* G2 {9 M& `
A                 . }% g  A* \& Q( T  z( [& b" u! k" K
a    2    0      0* c- s( h& g( b% I  Z
b    1    0      09 u1 r, v3 j9 ^0 U
c    0    1      0
# x' Q6 i: q+ f) y6 P17 G, N7 v! x; r- |
2  P* u' {9 T9 x- d: ^+ m
3
# `- Q* Q* V! u; p5 O& G4; ^" y* O. S* r( Z" u
51 h- |" k5 s) N- s1 c! R$ n0 L" f+ g' ~
6
. ]  _; W6 x  J) c- ^3 W/ @7$ m. H" t9 A: m
8
6 @' L  c; u4 b. y0 k* x99 K* O; \. B7 k' r( `, u; r7 G
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
$ V& W: ?* ]2 J0 S8 W- \; e
" y* X3 S0 t8 f( D3 XEx2: 钻石数据集4 s3 {, d, y& p8 l9 C3 s% ^
  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
! p: K! h/ L2 d$ I; b3 q5 M- R4 d# T* S
df = pd.read_csv('../data/diamonds.csv')
( G5 E7 }- ?, H! k; E6 t' adf.head(3)
- B0 W4 p5 ]3 u) J/ z. _. q
2 z# b) }" A7 ~, \' HOut[76]: ! ?0 \( a+ g. X* J8 q
   carat      cut    clarity  price
" [- _( H+ z7 M5 F, z* ^0   0.23     Ideal     SI2     3260 m* U1 R4 O2 e
1   0.21    Premium    SI1     326' F# c- l3 K$ O$ e5 J
2   0.23     Good      VS1     327+ I2 ~7 b! o" D8 q% d, P( f- ~
1% f! M! R' |% [
2
3 A& K7 |" F  V3" @6 q4 ?4 t3 x' s+ q) q
4
+ U! o8 w" n4 m57 s( F' S4 F* Y9 y
6
4 x8 ^7 b+ }  ?3 |8 p9 G$ S# v1 V7
( c) l& y5 c' G2 P* o8
) Q- {' I/ [/ m分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
6 g$ H6 Z! @/ P! z! P8 {3 f; U& c钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。5 G1 q! a  t+ o$ h/ T; Z
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
' `! }" |7 R  l! _$ }9 k3 |对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。" H- N, `4 z; L; }+ G" b5 e7 b
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
/ J: z( T! p) J9 j, f- ^' F对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。" `# I/ K7 [( I4 F
先看看数据结构:
' P! A0 \5 Y2 q0 {, W1 |$ \' J
  q1 G. Y0 j& I# Ddf.info()
# g3 P9 r6 M: V8 r, kData columns (total 4 columns):& A8 m2 k0 R- j: C5 f, l. U
#   Column   Non-Null Count  Dtype  / J: e! \9 l' E4 f2 V0 F/ @
---  ------   --------------  -----  
7 {5 i  i1 `& v4 n& R0 O$ m 0   carat    53940 non-null  float64. m* r; i, W6 W; r/ y
1   cut      53940 non-null  object 1 Q. V' r. Y/ d0 _6 S4 a1 c7 W' V
2   clarity  53940 non-null  object
( f* N. ?9 E2 ~  N: L. ]) I2 _8 Z 3   price    53940 non-null  int64  
4 l3 R# ]( O7 @$ p# ~dtypes: float64(1), int64(1), object(2)- B5 G. r+ Y) J
17 |  Z7 q0 x' l9 ~* o# c) x$ M
2  n. S8 d5 o" L, W, l9 f" z
3
3 Q- R; K8 T  S4
. j7 Z2 J* D$ y' C5- T1 d( \- ^9 k3 Y7 I3 @# `
6* `/ |: I8 W/ |6 @6 C5 a6 ~
7& {, a+ B, U  u9 S
8, B$ p. b( `( O
9
% L' _+ f- ^  c比较两种操作的性能
1 M' w; ~. J5 H* y. N7 G) G%time df.cut.unique()1 o" w& t8 {5 w0 Z. I
$ d7 [' A, O2 u5 {6 g1 t0 X3 K
Wall time: 5.98 ms
+ r" l& I9 Y: f7 [" S7 Xarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)5 E3 A* h* v# Q. i: Y& g
12 D- n" n. a$ ?/ @+ d
26 W* w% V( k4 W' {" q  P
3; U) r  A3 A+ q2 J1 z: _
4* F3 \. ^+ _  h( s( k
%time df.cut.astype('category').unique()
- H7 Z! u* V. ^; K$ l
0 D% S) T: V( ~6 x1 ^8 SWall time: 8.01 ms  # 转换类型加统计类别,一共8ms
; V+ z+ I6 I4 G4 \['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
* X% S" |' ^- A: j& E- E# C% C; s1 QCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']- j% x+ s  g6 N1 [/ z  g0 t
1
7 e/ h8 s2 H7 ?. e) u27 h! Y% K) ~, f# w+ m1 W6 H* S
3
4 _1 |8 V+ I9 }. L45 t6 J2 S8 s/ A
55 ?0 N( N0 Z& W. ?0 u6 c: t
df.cut=df.cut.astype('category')
7 m9 w% a# c* ?; s/ `* Z- Q* H# [%time df.cut.unique() # 类别属性统计,2ms
2 n9 p5 z1 D3 f! \1 C" y, K8 ^9 z' r6 h; o& U
Wall time: 2 ms4 C+ ~* X2 h& A8 a
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& I' ~# W: ^2 @/ L2 {! a
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
: R4 I8 ~1 Q% g. L6 x: c# k: Z1
7 U. P9 ?9 @5 W5 @2, H7 `, R# D# ^9 ~" Q9 b: X$ [
36 M) C1 k5 ~  o/ L& _! I5 |4 h
4
1 w. I' w0 l: b- t; e5 F. n5
+ E7 S0 }" A, y+ W- r0 [6
% o  k" [9 ]4 z1 L$ w对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。# v8 J8 c/ u1 p0 ?" _
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']0 M( \: P5 f' E) Q) l, m1 [' o
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
) t! K0 I& I6 j  Cdf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换) O+ |! `- O8 R
df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)" q( |9 Q; E5 l4 h4 A% E3 f

! c: H0 k5 D! Q6 p. Qdf.sort_values(['cut','clarity'],ascending=[False,True]).head(3), P% B1 d) U$ |* F  N- u. m

  q; _' e& s. N: a        carat         cut        clarity        price, e, u( z- g. r, k, n
315        0.96        Ideal          I1        28014 w# Q* o' o1 v5 g- E9 T* V
535        0.96        Ideal          I1        2826  I! G9 E1 P% Z$ [( d
551        0.97        Ideal          I1        2830
: z5 d. c2 C4 F3 o* S/ y$ t- |11 y, F1 x( f+ A! C- e) K+ H# r
2" t. w  D) O; O7 B7 ?/ [4 m. Y; {
3( z( h1 {# r/ a# n4 E% K. L- w- Y
4
/ }1 O3 M# t1 K  C: n5
8 @$ q* }5 R2 k) y8 U% X) Q6' r' _1 b1 H4 z; h
7, H$ S& |* x$ W: Q  m
8
. H( E, R1 R9 z& I9" B% }9 r9 }( ~% N
10# _, T+ S8 M* K2 v
11% [( F: R& G5 l% P, `1 C. ~
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。; o! |$ z% F2 ]9 Q( `
# 第一种是将类别重命名为整数  j& c% V+ ]% ^/ R' @1 X9 q. m+ [
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
5 a9 {0 E$ V+ |0 S0 V# A2 L7 idict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))! H& F1 |; w7 h" X2 B& w

+ v' }: [, T2 r/ |df.cut=df.cut.cat.rename_categories(dict1)
* {; L: s$ A, y; ^6 L! N( W7 ^df.clarity=df.clarity.cat.rename_categories(dict2)
% Q0 C! X$ ?7 }! hdf.head(3)& e0 A/ h1 X* U- u4 ^3 a+ a

4 b  l6 A! B0 P1 F, w' K4 z        carat        cut        clarity        price
2 Q8 Z1 C; Y2 |5 _6 u( F1 `0        0.23        0          6                326
- S# o0 O; s1 O( Y1        0.21        1          5                326
% t. g8 B6 ]% b2 H$ a" d% G& z2        0.23        3          3                327: k/ ~8 O1 X) j4 @  i+ ]3 U" u  O
1
8 c5 ]# N7 G1 @6 [$ s# }# z- O2
* r" U/ @. j7 l" k0 M3 U4 {5 j/ k3
& O& k0 x+ W" h4
- D( c# M& x3 P' _" _' b8 n  B5
1 E) q, C" h5 b* p+ h6
" M4 ~# O" Y! E" D74 O: `2 m& t. C- r& i
8
8 U' `$ ~* u7 p+ C9! F7 V  I8 q9 z7 |5 p
10
* e' `3 Z$ T; Q* ~  O, w11
/ }; c7 u& O* N- H( J7 E+ {) q12" b" O" S0 {; j9 y% t/ ~7 q' k+ [( Z
# 第二种应该是报错object属性,然后直接进行替换3 N. O# \) d3 d  m/ ]
df = pd.read_csv('data/diamonds.csv')9 h8 L% a( \1 K& U& u
for i,j in enumerate(ls_cut[::-1]):6 g/ D  w, `2 U0 Z  y; f( {
    df.loc[df.cut==j,'cut']=i
& a) G# @+ h0 b5 N) o+ Z5 z% M0 |# W8 u5 n. r0 e  }' Q' h
for k,l in enumerate(ls_clarity[::-1]):! l4 a1 H' W# Y& i6 z0 D
    df.loc[df.clarity==l,'clarity']=k
' u$ P$ i3 ^/ x% C( Z! }0 w; odf.head(3)
- X0 u% W6 {; L1 l1 c
: X8 N7 z( s! @$ c7 p        carat        cut        clarity        price
# c! u$ R6 h0 b6 k* c0        0.23        0          6                326
& \! u0 t( j# j* w5 ~1        0.21        1          5                3265 a2 `8 X& G" y, `' C* S5 n/ {) ^+ p
2        0.23        3          3                327
5 p  U4 v0 P# W$ w1
5 F0 v; S& J: n3 Z1 U$ x# `! R23 g: {- v, N: ~+ N$ h* E. ]! ^
3
. M! |8 `  S  p' b1 F( y40 `5 d, i* t- R0 Z( D+ X+ G
5
& P; l5 @3 Q) U6. {4 b3 F* X# w- F
7# k' u$ @; a% i5 R
8  s% i  I7 o7 O  n* @. j
9' `5 `- o; a( K( X: j
10# q' p7 |3 T; a) b  g: O; a; O
11
  }! o0 Z. {' ?4 q6 N128 e6 D% Y- C3 J- R/ y; i
13
  s( X0 l9 F# C( g. x, r( q; U& J对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
$ E1 R2 v7 D5 f; f: g- Z# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
' Y3 |! D" O8 T# Ravg=df.price/df.carat
. @; `/ ^+ ~+ G0 O% `8 E- j/ I! X. Q. V
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
# s! U# S2 [. F1 O                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
6 M% S0 ^8 ~3 @2 s2 |4 o6 }  }( G
/ P  v1 O7 ^* i" Adf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
" h, H9 c1 u; H3 V" W* X6 c' i                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
/ w' k: r  D4 Jdf.head()
4 x: J6 Y" o+ ~4 x5 v& k  M0 ?2 i1 O) z. a1 R
        carat        cut         clarity        price        price_quantile        price_list& P# ~& [4 E9 _  ]
0        0.23        0                6                326                        Very Low                Low
8 l. S% C, F% T4 d: o- w7 n1        0.21        1                5                326                        Very Low                Low
- P, {; T9 x* P+ l$ u# L$ D2        0.23        3                3                327                        Very Low                Low$ V  d/ V6 F4 R. P4 a, h1 }) W
3        0.29        1                4                334                        Very Low                Low
) E8 f7 g4 z7 c1 D; _. q5 [4        0.31        3                6                335                        Very Low                Low                                       
- g( o2 d" ]( F
' N% G8 U& ~9 y$ W1 K$ J; T1
8 z, _" J3 K" c; O+ I4 D1 D& V2
& z# l7 M+ k" v  \3
' K& j/ M& D2 L- L  M9 Y- c# D4
4 W& B' g5 J7 `8 ^/ q6 q5) o& ?' N5 Z( l
6% ]' G- b- z1 O, i! g" O7 I5 }
7
0 L& x  I! g" s8
: J3 ?: h, J) e1 Y9
# E) t( g  T& e3 m10( E+ h% T2 H8 w6 m- v! c0 Y% M2 z- T
11
7 z1 P: w6 B& d2 I( s$ |12
$ t8 l# k! U4 ?2 z% A' p/ E  g+ E13' E8 u# r# n4 \! s' P3 p6 e' A
14% ], Q0 F( E9 {" D' C0 @
157 R- V/ W) f0 h, C
16
4 k) t* X, R/ g. }* C9 @) l7 b6 p分割点分别是:
8 r5 r7 Y* S" }) S7 G7 Y3 Z# s9 @( v& ~2 V# {6 H0 G
array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
8 V6 l9 ?& c) `array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
; c8 c. m0 I: b( R7 A  S; n1 O1
% x3 |, `7 L% x: w* ^" e2/ @1 ~3 f- ~  N( W. N
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。$ b+ p2 Z( a( a+ m
df['price_list'].cat.categories # 原先设定的类别数
7 f% x+ S0 A2 U7 RIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')& M9 U; h" J  X. v! ~
# Q0 P& N3 b4 ^/ t$ R! L# ~- J
df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别! N3 R. n* n6 D" X
Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
. K7 Q" o4 @; o$ F) Q. b4 N" b10 i% h) K/ |8 O; l! j
2
, L% H( j6 k; U1 s6 J2 c( L9 O3" s* K: x" m# o0 o: j
4
  A5 N* m3 L# e& G. D! l7 z5! `0 I) m8 q" s* z
avg.sort_values() # 可见首尾区间确实是没有的' t  l, [/ l& _* x
31962     1051.162791
: Z' r" z* ~. I/ g15        1078.1250009 b7 m; y$ c: k! A) A* P7 V4 N! I# k( K
4         1080.6451611 ]% G! Z: }) K! N0 U3 u: |
28285     1109.0909098 i. f8 ]3 f6 @  i
13        1109.677419
  L# q5 b* Z* E6 {             ...     
2 s1 O& @2 _' U: i26998    16764.705882
, O: Y' O5 W* t/ E2 f2 h6 Z+ ^27457    16928.971963
! t; K7 |4 n& e" x) j# }* a2 O27226    17077.669903
) F  c# E. g% E7 M" J27530    17083.177570
( \5 s# l: A$ @" O3 {1 z# o27635    17828.846154
6 J) b2 t- Y' k; ^# h1
0 C* s8 x: |5 w2) b$ [* W, ?# c+ w
3
2 f, a1 i# j7 o9 Z( t4
7 {" e+ @4 U9 Y$ a0 ]& b52 r7 X- B$ ^( f4 Y" _
6$ V: _, e9 F4 s; _, H" D: n
73 n# a; G2 ~7 U: {/ ?' A7 ?: a
8
" B: A1 ^, G5 l+ G- l& [' p6 j8 Q+ E9
$ W0 h2 C6 ]: O. p; l$ }" b% F7 L3 Y10
, Z" T3 Z5 Z0 `- S11
6 ^) t& R/ R" u5 X2 v& t12) ]4 W: s  n  x9 v2 E! L$ {
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
2 R' H! g% Y  N+ \! Z# 分割时区间不能有命名,否则字符串传入错误。9 k$ p  ^5 J" v
id_interval=pd.IntervalIndex(
3 b4 J! B% z  }$ P2 t    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
& F) A1 C7 `/ n                            )
" {$ r! n& P( |/ `id_interval.left
% x: I( L$ j# n+ Iid_interval.right
0 B& b; @$ e+ P. ?+ lid_interval.length                           
: B' c" e$ w8 Q7 r: {1) f' N" e0 H9 E2 _' n; x+ B
2
; L5 J; I* ?: _3
% Z2 y: @# h* a4 W  t4
3 t" Y9 p( L& Q3 c- Y5
0 p- ?3 {, J  z+ e5 \  k: u5 H6: ]: [% Q. X/ ^
7
/ C! ?; y2 T3 U  w) }第十章 时序数据, w4 Q- ?7 J' F) f' O' z
import numpy as np
" t! x6 E# ]# h, _9 v- uimport pandas as pd
3 U, U( ^' E: j0 d  Y- ]% x1! P) l( f0 w( s  z1 H% A/ \" w
23 c9 b3 X  o( y$ l1 \  d; N

3 c$ R0 T# G. O/ N' C. u* _5 m; f2 E" M- m8 v3 f& F: V
10.1 时序中的基本对象
  K; l3 s8 p) A5 T+ Y& h5 J5 B9 f  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
9 f) t6 C: t( w1 L5 q* G  x) J& @6 R
会出现时间戳(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的简写。
# c1 o& b- o( q) W  R: `$ B# Z6 d1 X5 n7 y3 P3 a1 R
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
7 k+ q( z7 F0 M3 i$ T) G" j- ?
& |, N. W+ C$ F: @# m5 h7 y) \会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。, g7 \. g+ t1 M# y# ~) n/ G

* ^3 j& x" }; q- X会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。# e1 e: g. l' ?1 S# |- Y

0 i) i/ M) o6 `  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:( l/ G$ e' N" p# ?& b/ a
4 J3 x, k. T: G& ], s8 F% r. Y, ]
概念        单元素类型        数组类型        pandas数据类型1 J, T4 T; G( D( |
Date times        Timestamp        DatetimeIndex        datetime64[ns]
7 ^+ w" T( u7 e% e& ~3 I* W) M  l8 U# ZTime deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
/ ^( O/ J. z' u) UTime spans        Period        PeriodIndex        period[freq]  u1 P4 h5 ?9 p9 ^) \0 I2 @/ P
Date offsets        DateOffset        None        None$ g2 O1 h  u' @9 B' ]
  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。- O  O7 r. _* ~) ^4 J
! y0 Q6 K. S3 k
10.2 时间戳
( O* U: ~8 y0 K% C* w10.2.1 Timestamp的构造与属性- V, A* p. e+ y$ B) e
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
5 a; P: Y1 Z3 B" f: M* V9 g; z
$ c, K. S5 E8 Nts = pd.Timestamp('2020/1/1')4 d/ E; p# A$ ?# r! a) n
3 x: M) N  z+ D7 G
ts( d$ Z5 a/ b) W# Q: ~  _
Out[4]: Timestamp('2020-01-01 00:00:00')  z) `7 c5 Q- l3 L. Y  J- I+ p5 G

% E4 ~" e7 T# g' {- R% ]  ^3 N, {ts = pd.Timestamp('2020-1-1 08:10:30')+ V% R- y0 A* I6 d
0 p* r! H+ L  I
ts
5 a+ ~' U8 |1 e2 b1 |& R( ~Out[6]: Timestamp('2020-01-01 08:10:30')
$ w" j. J0 ?! b, u1: K# W* Q$ J' Z$ ^# ]* d
2& j1 \: L; y) P4 r' K
3
) S6 d% k! g' y+ F& u4
: B( C) R4 l  p# W  K' i5 E5
! G- i1 C0 c3 A# s6 F  B6
  }" O: e! w* M# I, Z& X7
! H' p3 F- P: M& l- B85 Y. }) N2 v; q( |( u
9
* X/ I4 W) ?9 I/ }- t% C通过year, month, day, hour, min, second可以获取具体的数值:
6 e  \1 j/ t* S$ M5 \$ J
9 Z& T$ I+ h& {3 {4 ]ts.year4 _1 S: q4 ^, T4 R6 @
Out[7]: 2020
& B5 |0 I% v4 H8 r/ g/ H+ K  T1 |" ]2 w) D& h  {6 R- ^
ts.month
/ i% G, C- p3 e4 H# L- v" TOut[8]: 1/ J  Q2 {- r: {5 k% g

) e; f2 Z2 |$ [0 Q/ v2 A1 @# Dts.day
! W% k2 e# f. I) O% q0 b$ QOut[9]: 1
6 V! i9 \5 _# i" b
$ X0 u* u9 L+ ~8 |ts.hour
# A$ _, A7 P9 s; \  Z, w$ \! zOut[10]: 8
# O+ ^" B- t9 e7 G9 @+ m( R/ ^2 V, S" k% T/ u( v9 E
ts.minute
4 X8 G# ~. N. g' Y8 k% zOut[11]: 106 m0 {; D7 [% G) Z. s* ]3 |

0 L( m& n' c* h+ t" Dts.second
$ {- j6 l3 @1 EOut[12]: 30
$ f! h$ Z9 G) B3 \
& N5 ?. D1 x/ X7 _" J# j. }1 f1
" t9 l7 u- _0 l3 @6 d0 U& Z8 i2
+ D0 M3 v$ v- ?1 _8 o3 Y3
+ h4 Q) M5 T; C+ F46 w& g% V! M0 t7 ^( }* {
50 U0 \% @  k# h: j& a! E2 a3 D1 E
6
3 \! Z. d6 N9 Q/ H7 _" T7% X9 J, n3 L# Z2 w
8. E6 S, c) {4 J$ t( X
9
% P0 g: P/ C, u0 H1 j) j$ P10
; \! s# ]2 w" g, [! P9 |0 G2 B/ t11
8 G: C5 t) a% [129 {5 C5 X" r# X" w+ c
13
" k( n2 ]' @6 Y0 Y2 P14* Q, s( C; N1 r
15
( |4 e# `: a/ e16
- w, d. K, m4 p& [/ n17/ K+ ?7 R  J1 ]+ E  ~- e1 y9 ^
# 获取当前时间# h1 }5 r) b3 F8 _! N( R# I" \. o
now=pd.Timestamp.now()
; O% {" `( J" Q, i/ s/ X1
; w  r1 v1 g( b; T2
' S, [6 _, O4 }0 \9 F# g- ^在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
4 ]* O/ N3 h' E! g4 q% _$ eT 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)5 H$ C8 I( |/ l# [0 U- t; u, s. M8 ^
TimeRange= " g2 H6 W& o+ A. \# x/ o0 `7 t- I
10 0 \/ A2 c8 d- w5 k+ Y
9
1 N( S' c" s3 w  H+ S, m: D3 S# }  } ×60×60×24×365
% C' i/ a1 D. q5 k6 K4 ^, D& l2
( t( a9 E6 |! x. ~3 w. Z4 T+ v, L64
0 B$ h- C" j/ Y) o
" V/ A) ~* W. _1 v4 A: E) s. L8 Y$ p- p9 G
≈585(Years)- E$ Z: ?- p. h  {9 N- ?

& t* p8 w9 X: K$ P. P8 `# ]通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:9 c# H1 ]  D/ U/ z: P5 A7 j' t

2 E! H2 r  `  N& kpd.Timestamp.max
; f0 Q; |# J! S( I1 e6 U1 xOut[13]: Timestamp('2262-04-11 23:47:16.854775807')& V& C1 q7 u! S* |) S9 `
$ p& I: t& @- O* O- B0 a3 K# _
pd.Timestamp.min  D/ H$ K) \# @2 L2 x6 F, k" T
Out[14]: Timestamp('1677-09-21 00:12:43.145225')
: Y" t1 ^7 j/ L1 r% ]  N* o$ p7 T2 k
pd.Timestamp.max.year - pd.Timestamp.min.year; Q; e5 s  }$ }1 j/ w6 q
Out[15]: 585
/ T- _+ N+ z# x* A8 {; z1
) |, D0 N, x7 {( C2
, y# W1 _* Y8 ~& {3
2 x2 ^8 Q1 e7 a! B% H4
! p/ z7 p& q: v7 L1 D5
' R2 K) ]1 {4 v0 b6
- N: [+ L' z/ e& y0 t7
/ K5 G- v2 e2 q6 I- c* c8
1 R- f8 ~8 G- |0 z10.2.2 Datetime序列的生成) _4 ?3 x6 f6 }% D* a
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
1 ?. f: K7 O2 u" q4 x0 n                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True); [7 C" O- B. [5 N. D
1
3 N+ q' ]* |- b" e$ G  o# x$ {2/ Y) D9 W% g  W- C" r  o
pandas.to_datetime将arg转换为日期时间。
" a$ l# p4 u  L  C% H
1 P& t% n1 N! r. y3 s7 A6 u; qarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。2 k8 D: r: Q. Y" m1 W! \
errors:
  S2 H- Q9 y. T9 \3 X7 p4 H  Z- ‘raise’:默认值,无效解析将引发异常
. l0 G6 ]- H5 S7 i- ‘raise’:无效解析将返回输入
( Y. Z1 b' w5 \' W" T7 y- ‘coerce’:无效解析将被设置为NaT* [1 |) m7 C7 S# s3 n
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
, z3 O+ t7 X& Y+ |7 F, X5 w( W. I$ J* fyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)5 G0 P; n9 Q8 {
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档/ @9 p: Z' Q* s8 w' R2 J
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。; y8 z4 Q3 @8 P
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
, B: P2 d- t: ]' ?9 ~) ]" C2 S5 m  }to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:' F2 t5 C% A. L# t; t* ]' D
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
! R7 V8 C4 ~0 j* t
( ]: W# P0 y5 @& Z! d; oDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None); x1 N; C: R0 J$ Y) e) o
1
/ c3 _6 R3 L9 H+ H% L' L, F" z7 i2
8 A' D& c, e% a" K- Q+ `* j4 B3
. e( O& j8 a( L: `在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:: d) u1 m# ]9 u0 }

4 b4 N, }+ {0 j, W( ^2 Vtemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
  R3 j1 }: c8 Z5 [- A5 H- E+ ~% Ytemp
: K! F- ^- S! |! A
6 [, |  G" A- ?$ f. YDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
! R  F4 N' ~4 O! ]# \. d( n1
  m8 D$ b5 W2 g* d- Z1 Q/ j2
1 M( T' j  a+ U6 N! N- `% p6 E* x37 y; }9 M; I; K$ p  W; X
4& T1 e0 S% O% T
  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:4 }0 \& w) E; [  H( e, I

2 s) c0 [* @. X* h' @7 X. I, a9 q$ {pd.Series(temp).head()
: t3 o' B) n  [7 Z) n- }" m: \5 a& X! |% J
0   2020-01-01
; o* F3 e. {4 Z& E( A; f2 t% B' H1   2020-01-03) m5 t/ j) V8 t; b6 j0 y, j4 O
dtype: datetime64[ns]& R0 ]6 [  w/ L" g1 e+ ^# \
1
# ^  F5 G/ n* ?0 ]8 P2
6 O) v6 o! j( b0 W( ^! Q0 n, t2 p7 ~3
' e2 ?! ^+ S6 _' \: v' D4
$ V- z/ q+ Y* e) l5
* p4 {: d0 ^& X+ [' y1 |# d* t下面的序列本身就是Series,所以不需要再转化。! Q5 J: Z% q% @5 l) m
' F! C$ S  r9 Z0 u  a
df = pd.read_csv('../data/learn_pandas.csv')( Z5 O9 c! R$ o  m- q3 c
s = pd.to_datetime(df.Test_Date)8 w! s; f* ]' O; J) k1 g
s.head()
+ ~' Q( b+ V- `4 M
6 C! Z7 {+ D% p) c" Y$ r0   2019-10-05& s4 h8 E7 W3 W" `) r
1   2019-09-04
8 }% Q3 Z4 D7 H7 t2   2019-09-12
! q4 ]' C$ o/ g: h1 H$ Z# N5 d0 Y3   2020-01-03  m1 M' v2 o; b
4   2019-11-06( R+ \7 r7 b& E5 M$ @0 E. z
Name: Test_Date, dtype: datetime64[ns]
! d8 N& |" Z& [: h1
6 h6 z" t* ?3 Q' \3 J& K+ K2$ G8 L4 [5 ^2 r/ l9 G/ ]& R, w
31 _6 g' Y4 P) \; R/ y+ [+ b. F2 L! G
4
4 X. u: _* H8 [% L5  o( Z8 L3 q9 H) r6 ]
6) w: I, ?! y# f
7
% z& W  ]- y1 ~+ }  j8
: x9 h: I' H$ f) X9
+ A( e8 G4 y) O& g4 z10
' z& e  X3 g0 o7 [* B/ q# D把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
5 g- Z* S1 q! [$ o0 V9 u; `df_date_cols = pd.DataFrame({'year': [2020, 2020],
& y0 c: ^0 l: k9 X; y2 w# d                             'month': [1, 1],9 ^2 C. N- ~7 D: \! l$ K
                             'day': [1, 2],: O% `5 i* j" ^) \
                             'hour': [10, 20],
% ]' F% E: h8 j! F, _                             'minute': [30, 50],; ]0 U/ N4 z4 R/ G
                             'second': [20, 40]})) B6 ], T: r9 e  z+ L) _
pd.to_datetime(df_date_cols)
5 @( K0 D! |# E. w, D7 u
, R; H& x7 A) ?% n7 F0   2020-01-01 10:30:208 a- S( n: f$ c  N3 E# X
1   2020-01-02 20:50:40- v3 m/ t; C9 ?% z- L; n3 k
dtype: datetime64[ns]
- S6 g5 ^( ^* l1; Y8 Q& i" Q" O* B
2
, r) Y) M) k6 q. e6 Z+ f3! P* U, P. V4 _/ r
4, ]3 |  w) E0 Q
5
+ ]$ m$ z, x7 J! m6, U9 P0 M9 D3 w* L
7
- I3 t0 C% {' S7 P8
8 r9 Y) u4 J1 k8 V! M; c8 C7 t6 ]97 _& s+ c4 w. W9 {% p
100 s# \# e! ^2 L5 E
11: r! J- B! p' i1 G8 v3 l
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
: y# Q! _1 t* a, O/ Q4 Kpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含  S9 T0 g6 ?. O' `
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
. |# P3 x, r" j5 D1 H- w& S2 v' G# M7 p7 |# I5 ^- g, H
pd.date_range('2020-1-1','2020-2-28', freq='10D')
* L, S0 u+ b- C5 l* [! [* oOut[26]:
5 A$ e: |+ O  r4 sDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',. }4 t0 i7 O6 v8 z) O8 r& r
               '2020-02-10', '2020-02-20'],& X; [. c) R4 U5 ~8 s
              dtype='datetime64[ns]', freq='10D')4 `) }+ d/ G4 l7 B" R6 c
  n1 H- h6 t( p; i
pd.date_range('2020-1-1',+ b$ r! A8 w. B# H5 c# S$ R
              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
& s# f' I. P1 Q
2 v+ S- f' j* D% B" \. j6 f6 O) \Out[27]: * u' s5 z9 N( B( m" Q3 Q; p
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
# X5 K1 b% X7 O2 U- x" K) g               '2020-01-24 04:48:00', '2020-02-04 19:12:00',4 k8 s$ F, m! B; q. U6 w  S- }/ }
               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],/ J- h5 ^' C) r" M
              dtype='datetime64[ns]', freq=None)9 s0 o/ ?: W& ]+ n; e, o/ D
  h' V- {" \1 a- _. E9 P8 q9 b
1
# V# Y! K6 O! n6 ^1 d" T2
, L* z; i. Y: ]4 P3 o+ t3
) d+ \( a' z" V2 ^3 G2 }! O  H4' h; @$ i: g+ Q' z9 ~! N, A
5: R, u0 Y7 Q* L7 n) F
6/ }, M( ~5 w) L, i! _7 l* s
7. W  k" k$ y3 V& ^( n
8- }! r8 t4 M9 \( y& S
95 H2 }' }! X1 p2 I- n, y1 a
10
9 b" B2 }; W" s0 |; M; h11" m9 ~# i( T- h
12
6 a. t$ I" P5 I  H/ |13
2 T0 K( u" ?+ k# n! h, ]9 e( l3 h7 w14
4 G( @+ E. L+ y/ T# d* q15
. k9 E- m7 e1 ^, C( n, ^16
& S( [6 V+ @( ?) b9 m- {8 l179 x# z5 A1 J5 j9 V( c8 t
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。* S8 T. o) z' Y) F+ X5 n7 d

8 B1 K5 b" e: c) I. E【练一练】% w0 s7 ~) |' a" }* k8 C( _6 f
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
* r& a0 d9 q3 O% a& l" W. O
$ z& O6 v+ V8 N) d; E- @ls=['2020-01-01','2020-02-20']
& P  r7 Z, O8 i  G# G* O3 edef dates(ls,n):
  I2 w' f7 ~+ n  \% B  z    min=pd.Timestamp(ls[0]).value/10**9
+ ]% ~: J9 p% _    max=pd.Timestamp(ls[1]).value/10**9
% h$ j& T9 \" s' p5 k! H, g# o5 Z/ ]    times=np.random.randint(min,max+1,n)
! n* t) H" D6 |% }    return  pd.to_datetime(times,unit='s')0 P/ @  G9 j7 `, B2 i
dates(ls,10) 5 K* z* u" l0 y$ ~1 K( V

# Y% _9 `3 A. G  H; VDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',. `. d- _* \. _2 L; |+ L1 q3 ^
               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
% I. Y  m4 i- V' r1 S3 m3 Z% X               '2020-02-15 00:18:33', '2020-02-11 02:18:07',3 c7 {# \0 M: H) m! i% g0 F
               '2020-01-12 21:48:59', '2020-01-12 00:39:24',
5 e8 y( [. ^* |" O5 j" Z  L9 b: K               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],2 \' [" g& {; f+ s8 O( ]0 Q: v3 h
              dtype='datetime64[ns]', freq=None)' z- g4 G, s2 Z+ E# k
1/ }/ l$ u+ d& m  L5 {2 w
29 e9 ]: V9 ]1 ?5 z" n& u
3$ @0 R6 \9 J3 u8 R
4
+ O7 h/ K" Y/ h% I% \8 M5$ ^4 n5 b: K7 s( r# d9 C
69 v5 h0 c1 L5 @, q
7" ^- k# a- H+ H4 c( ~6 o. S" [
8
; k$ N5 l2 f# W" S2 T/ O( U0 U90 @# u7 p/ F, ?
101 a$ r: o$ s: ]5 E  Q; G9 W
11
1 `* F1 c/ ?2 `128 M( q/ q3 f3 V( f& l& U
134 T2 U2 B: {7 w" u% R6 R& A3 W6 m
14
" z% E% r7 v" Y! z) m1 E& _8 N: nasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
6 m' @" y9 ?+ g2 }) J/ Ks = pd.Series(np.random.rand(5),
& P. v! l9 z6 Y, T/ j2 q            index=pd.to_datetime([1 L# ^  }- S$ A% a# Q8 \
                '2020-1-%d'%i for i in range(1,10,2)]))
( S, x% y& ?: ~; [/ L9 M7 K5 X* |  ?* F8 f0 c
" a$ w6 j  R+ j7 \& u  z/ |
s.head()
  c, l' u; v0 z# COut[29]:
" s8 [- r% Z  @3 n2020-01-01    0.836578
' Z& g7 x0 q; R; i2020-01-03    0.678419+ d! g; D9 M" u: m0 i" a$ J
2020-01-05    0.7118972 F" ]8 B* J, o) T
2020-01-07    0.487429
. x2 N& f( Q9 i9 f2020-01-09    0.604705
1 f- p, @' [0 M* G, x4 mdtype: float64
8 A$ @8 p! a+ U* T  c% z3 J# o% l
3 W/ {; X( ~$ F; ks.asfreq('D').head()7 I% E0 S0 V, E  G  x) X% z
Out[30]: 5 X' z5 P$ ^6 c) }; C3 k7 X
2020-01-01    0.836578
: m1 w2 t9 u/ F+ _6 r2020-01-02         NaN
) T: W3 |; ?: W) L9 W2020-01-03    0.678419- k( N; N0 h9 ~/ D# R- E$ j
2020-01-04         NaN
( ~" v6 Q) r% `: j/ F9 i7 [0 s2020-01-05    0.711897& z' ~' }9 z9 q( e# f3 t
Freq: D, dtype: float64
, O/ t9 u, S  q6 q; i/ n0 c- b+ u* y/ K! ^. i6 n$ g! w
s.asfreq('12H').head()2 z6 Y9 D" ^+ R5 E" y" V4 Z
Out[31]:
. @; a' P+ @2 G$ q4 N7 i7 V: i2020-01-01 00:00:00    0.836578
5 m0 P) U  ^/ ^& u( V4 h1 d2020-01-01 12:00:00         NaN: r/ J( F% S+ U/ @! \2 y
2020-01-02 00:00:00         NaN
+ Z% D9 r9 A+ B2020-01-02 12:00:00         NaN  D; K; M# v2 Z& M
2020-01-03 00:00:00    0.678419
/ G1 m3 e7 w8 K- b* ]+ oFreq: 12H, dtype: float64
# b: z+ r9 M# y, S0 t' y- d7 \9 }" a  E
1
# H* b8 M$ O# S7 ~, U2. @4 r: F% ^4 g* X. e; K
3) {; j. w6 \1 }* `' a- z. i* a5 a
4
* R0 i. [! u3 s. F3 W/ l5 V0 p5
, a- U5 q  {/ T1 ~2 |( \% n6
+ ]- T3 v: z- c: v) U- E& a7
! A5 t, d$ ?& N! ^- ^" H2 Z3 O. Z8
+ v, u$ m2 [# D$ F; k9
$ x5 Z5 o$ [6 H# Z( u10
9 ^/ }  H' J; Y/ D$ _11: ]& b* ]7 e) e! k3 O  b
12
! D9 K: `- t, X3 m; n13* H; |5 N. @/ J* q
14- ?" }8 T6 @6 I3 l
150 ~. n# X: q/ l+ _2 z
16; j- x5 K9 Q  h# F2 p6 G
176 w* M/ D5 K* O' n, J7 I
18
+ c* {! ]- X9 f" |/ I0 \* r19
* u9 C# Z1 R: R2 Y' g- [- ]: t20
6 p9 o& k4 L2 J1 G21
) c3 K1 _# \2 O* \' H2 Y22: [3 R7 l) x1 W* O* t. `
23
( ]3 p# m) o# X! a3 _% \24  x" Z/ J- T3 X, b- ^
253 h( Y( c& d; `( J- |( @
26! X" X7 y4 ~/ C& ^
273 D/ I9 J; Y- k7 _
28( v+ A! f  t) o, z7 b) k
29/ c- W  O3 ]( K
300 F3 Q; ~! S2 B- J4 k
31, A, p5 Z: I+ c9 ^- J+ j2 J
【NOTE】datetime64[ns] 序列的极值与均值
9 u+ O1 Z! _" U; R* y  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。, m- B, _0 k1 V+ `! m6 E; @

  @. R9 ^4 W3 [+ ~10.2.3 dt对象- j; V- e' J% _& H1 z- {9 m
  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。: W) O1 X& \! M9 x0 n$ C
8 {  S- a' V7 C/ `- h
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
% q1 z# F- `3 Y6 [2 o: x; X3 t1 ]s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
3 A- T# x4 B3 h$ F& `' ]2 L" p# ]' Q# p9 }/ X; s$ m1 i
s.dt.date! i5 I+ o9 w. `3 s! G2 Z% W
Out[33]:
# z* I. _7 b: _7 g0 D0    2020-01-012 m/ \# `+ i# O! U* [4 A
1    2020-01-02
  |- u- I; _$ ~9 S: `2    2020-01-03
. x& p8 o' J5 K9 b7 M* p9 `; a, `3 O2 qdtype: object
; C, J/ S" U( }
0 @0 J1 b' J) k2 ~+ P* `s.dt.time
2 S2 Y5 C- A& fOut[34]: . g$ h' _# ~0 o4 ?8 b7 S
0    00:00:00
) {# |. [: u3 X( }2 K8 {1    00:00:002 L: i" q) ?6 c. f6 |
2    00:00:00$ ]7 H9 b1 q, q* @( C8 Y& T
dtype: object* _; v7 Q, L( X9 Y0 n  W
2 s, \+ F9 p# }& A' D
s.dt.day- n2 q) n2 n, ]- v, G* h' k  _. ~9 S
Out[35]:
9 f7 `8 @0 K3 a! x0    1$ M/ _% q8 @( [' C# F0 w
1    2% j( k- k" Y; ]3 X) N  M" h- w( J
2    39 b: i3 Z' W  D  b1 S. T' |7 @
dtype: int64  T3 p8 y( F7 l

/ J" P, q2 [/ y7 j2 u/ L, z+ as.dt.daysinmonth
, x* O. P& l1 m& X, z1 o" WOut[36]: 6 H& }7 _  M8 I, ]0 i
0    31. x5 l8 R$ X/ R  p( L
1    318 M- a. t% [. A# U$ f2 p
2    31; }$ ?% r) B7 z2 i( V+ i
dtype: int640 x2 h7 D: @# Q- U" j/ d/ [
# C0 h: B5 H6 H! j5 ?/ H- e
1  N# T8 p+ R/ d( t" }
2  y4 N$ R6 K" `9 W2 v  L
35 ]. U: w: u4 N5 d1 O, Y% r$ X
43 _( {& A, A* `7 G; E6 p# H. X
5! i- y7 \" F0 H: G' \, V- j3 T
6
2 K( [) M2 G& X7
1 D' S8 I, ?' m5 o9 a8  a  w4 o$ L  X! a4 Y& Q0 \: L
9
/ s1 t/ i6 y1 w5 S" O10
) n' q" I: v7 i! I  D$ F8 a11/ E; O. G& z$ b. C" ]* r2 j' a
126 V) H6 {. T" r/ y
13" W& Y( L& D  v" L) K& Y
14! w0 O$ w  {+ H5 R& |
15
# T2 O" Z& X# c: L. x& d5 T16
- h9 J" R. x2 |, R/ O17! {0 h* ^4 s% Q
189 @+ Z0 A) l3 c& j; O( {) }5 l! {7 p
19, W6 y7 V2 x. }0 v) G
20
. V/ S! k6 l8 C8 i* |21$ F% i: A9 l! X* }, k4 v1 d! {
22
% t4 A, i" @4 o; S- o/ y- l23- u! {+ R, Q) B" x/ P( Q
24# b0 c( o( Y# n
25
" }0 j0 `' O% ]1 o26
+ b! P8 }; I& D3 R8 x27
+ v8 v1 i. v6 ~% u' t28% T% K6 Y7 L; F+ v# J0 Q
29
- P. l1 H1 E! ~. R" }+ @  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:3 M4 R: z' m! d. n" {- h. k
* S' N4 }" f  B8 n5 z
s.dt.dayofweek
3 l& Y: h9 v$ x/ e6 z4 |0 r7 tOut[37]:
0 O% U1 U5 j  U5 F5 \2 {0    2
. j) Q0 s2 u9 r5 j$ Z1    36 j% l( J# s/ S) e, {2 e
2    4' \) w" v5 L9 P' p- g
dtype: int64
0 |0 l$ K' {- y! {) s! g) m3 F/ I1 ?# f# x2 b6 v
s.dt.month_name()
* A: ]6 W  u) |2 {9 J+ {Out[38]:
1 u- m5 T7 b$ n. K" P) r0    January8 d3 G: y! k( Y1 x
1    January
% T6 w4 A. {' p2    January) i1 n3 }6 m/ f. g$ \* V# ]
dtype: object" X- z! Y7 e9 \) H& W& ~# }
- S3 X" N( r* X# _) ~
s.dt.day_name()
0 C$ u. Y% `* v" A% x& TOut[39]:
8 \% @- @! w4 E. |+ \' _: o0    Wednesday
$ t" t1 }1 _8 k- D* S, l1     Thursday+ x# G4 R5 v4 Q5 C" J+ m
2       Friday
% G/ h& o1 `; X' _/ p9 ndtype: object& E8 t4 ]2 V/ q) O' g8 P

! G& ^7 f7 Y: m# n  s11 _4 F8 b+ q0 |5 T" ^5 B3 x
2
- _) y5 s7 k& ^* t6 H+ q3
, H# ~: b4 t$ R! f4 _8 p4
5 c& g/ Q8 ~- o4 S5
% T2 `2 ~& r2 @  J! p9 x3 p6$ {, j; P5 l$ ?: U
7
, F' h  Z% H4 O- N+ A( O: ~8
4 P5 j6 s! [+ D5 K7 P9
5 e5 e, H3 U& u+ b. W- |10  p0 w  h5 G9 V9 |; [+ X
11
) v% l7 E, k$ |/ |! C  g" z% p122 g% V! P9 t- A# D3 p; ?. t8 e2 o
13
4 n5 L9 ?, T' s* J141 |  M& f  D% ^* Q' E
15
. R$ L4 Z/ \1 x6 Q* F3 a7 \16
1 o& }/ a  L+ v$ N17
1 n, j* S  _5 o3 d4 r18
# v7 j0 E8 B+ I* A19
# y0 t$ j, ?; S; B20
4 l; L# a- y$ G/ c第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:" w# x( A5 ]! R2 x1 D
s.dt.is_year_start # 还可选 is_quarter/month_start
# x) y4 D/ `% f; z  j$ u  K8 U5 bOut[40]: & r- N* Q: |* m' E
0     True: ~+ Q; Y2 W2 b+ v8 p
1    False
' h; X9 F, d4 E0 o5 R7 t) F2    False
; v+ d% z4 E  j: i6 M7 [dtype: bool
5 [1 f' L. W; b* ^6 c7 W" s: w' [! i$ O5 c+ g
s.dt.is_year_end # 还可选 is_quarter/month_end/ E7 z' A0 C: {, T0 N9 P
Out[41]:
2 Z' k  @* _) K) f* f$ x0    False
& k+ U9 N3 ~# n' i3 A- k, B1    False
- x8 J8 ~7 r& S' B. N" a2    False1 E$ T+ ?2 E( R; S3 L
dtype: bool# i/ g1 |) `8 M  W' j: c; C: @! H
1
3 d. ^/ `8 a/ b1 G: ?  h2
8 o% U2 ?- a: t! ~/ p3
0 N2 F' V8 q' ~; a( ^/ H4# a9 T' g+ w2 J
5
# q0 Q1 y) V; ~6 Z4 [- N" C61 L) z$ F8 `( Y& s: M* b1 j+ p
7
4 \4 m9 m' P" Q- {' P1 Y8, A& Y0 X# k. I  G! U. t
9
  w, m  H# M( J5 ?' y- H5 r10
3 k4 w7 S. F( R11" {+ N0 T4 D# j3 ?' l! Q: G
12( t9 T( p6 Z7 J, z$ {
13  N& k1 G+ r9 q$ R9 q3 y
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
. M+ Z+ B, E7 X  [" N' k6 k3 ss = pd.Series(pd.date_range('2020-1-1 20:35:00',
" Q0 G) N5 I" d) Q7 q! i                            '2020-1-1 22:35:00',+ Y" W5 S: l& i4 H# X' M
                            freq='45min'))
- e9 _) k! R0 c6 g
6 F( k, [  ?: Q
' j" \+ v4 X) ]( es6 m6 K6 M0 h) [2 N3 n
Out[43]:
5 Y; ?4 a2 u/ n3 }! {0   2020-01-01 20:35:00
$ E& D9 h+ g( t$ G' w& K+ Y* Z, y  X1   2020-01-01 21:20:00
, D% [- z; K2 }% y0 j3 \5 I2   2020-01-01 22:05:00
. w5 `  K2 {& i  h4 b: `4 Qdtype: datetime64[ns]
$ k% x8 O0 `& S1 s! d6 U" P+ ]+ f& D; s3 L
s.dt.round('1H')* u" I- h% G  c7 J  I' a
Out[44]:
5 I4 N, e3 D5 ^" k- p1 o  [3 e0   2020-01-01 21:00:00' o/ k7 ^3 B% Y6 {
1   2020-01-01 21:00:00
3 |0 M# Z$ V4 @# [2   2020-01-01 22:00:00
* W, _7 C. c: U% |9 }0 N7 I% vdtype: datetime64[ns]
$ p; W# V+ a( |5 ^9 y* l$ K3 I. ]/ E( w1 q6 U
s.dt.ceil('1H')
& K! _2 q6 c( h. }Out[45]:
$ D# Z3 t: a7 |8 u- y. ?0   2020-01-01 21:00:00
0 d. ~/ R1 R& N5 i' E1   2020-01-01 22:00:00/ a9 Q1 \7 J# b& t; ]  D! |
2   2020-01-01 23:00:00
( `3 k  Y- W0 Q/ z0 Ddtype: datetime64[ns]$ \+ H8 u6 c* R2 ^, f

$ D0 V# C! }8 R: t: ms.dt.floor('1H')
2 |  |( u0 Q8 f$ r% ]! F' P# M" \( @Out[46]: : o6 E2 K: B8 {( U* v5 N2 e
0   2020-01-01 20:00:001 _* N2 y: L- N4 h
1   2020-01-01 21:00:002 i1 R  q4 d7 Y% o- M
2   2020-01-01 22:00:00# [1 Q1 N7 {: j
dtype: datetime64[ns]! E* \9 m( J5 Z/ `. Y

1 Q4 k1 q+ K4 g4 X! }1- J1 |1 g* v$ I9 }1 O+ Q3 Z
2
, n# ^. P7 C0 {4 G! x/ @3
, H! {3 e1 B$ y9 A7 F; ~+ `4
7 o# B- [0 z8 s. R5
& p5 r# @0 U  w; g  h6
/ f9 a4 v$ Q$ L7
) X$ M3 W/ n5 T! A, a# v5 B8
$ W# b# M! r1 ]$ `0 C- r; ~$ V96 q$ N3 B$ a: `6 p/ t+ O
10! k; Z: X5 C& i/ x$ w9 h( I
112 h' r. X" ?0 |7 d, P: X9 Y, C) g
12
' h2 l1 j$ |: k5 I! l13& x1 g3 i& w. N
14
/ t: t# E' `0 g8 V0 V' \152 l/ b- o9 H3 T7 p+ B
16
2 t# _+ z5 F% w3 T17. Y, _- L2 i9 \  D
18
; ]$ C/ W3 _9 q7 ^, J# K8 u- Y19% t% L7 z; f6 O. g
20
8 x& w  t$ E; z- h# F, ~7 X' C21
, Y5 T) U" T" W- d, {" g22  _+ ?7 u' b" N, \; h. s
23
9 \% J2 G/ p& b+ Z" }! L243 X$ T' t2 ~, ]3 `4 V1 y# o, k7 O
25  O! B2 S) B1 L3 g3 v! r
26
7 G- q. F7 e/ A/ o, H27) m4 i" }) q! s& L$ w5 \
287 D1 ^! }! @7 q2 r
29
! M- E9 ^% \& x9 z, a30
( M( j4 t2 w& @8 k. O2 C  C, G9 h31. j* q& o4 Y* j* @1 \
32& O3 j0 Y! c3 T8 E9 v- V
10.2.4 时间戳的切片与索引. Y% _: P" n. N2 T- c0 X
  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:3 P4 @4 s% \- @% k( g/ |# B

8 B7 [" e  y/ b0 f4 v利用dt对象和布尔条件联合使用8 x' R0 B# }- n2 G9 H
利用切片,后者常用于连续时间戳。  v, o: a' g7 g# x8 M3 b, Q, e5 y
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
: C  M2 G7 q/ Sidx = pd.Series(s.index).dt
3 V2 j; c3 c8 ~0 z; J5 rs.head()
- ?2 ~/ T% E4 O4 C* ]2 w+ v7 [7 I1 V" l3 R
2020-01-01    0
3 N+ \/ i# z$ M2 v; [* C, U. {+ E7 {2020-01-02    1
; x" _4 X0 H! l2020-01-03    1
1 T3 M1 Z/ i7 F2020-01-04    05 _% h  s9 Y, b4 b  M
2020-01-05    00 T7 @- C1 W  C9 ?7 S% Z. \
Freq: D, dtype: int32, w8 g3 P5 y0 T# j7 o8 ?! i
1
2 [  y9 `. a* D" ]+ H2 {2' e2 R# U5 X# y8 l1 a$ j
3
" l3 j9 r7 l7 `) T0 S4% U- Z4 _# E& ^9 S/ c% K: {
5
9 a# n( O- b2 D. _# I7 P3 {# x6
) E3 o; `" M- K& m2 o# F5 s  s3 ~7: s: E8 c5 J; E
8. g9 @& v1 ~: a% T# O4 O) w& z
96 s3 t1 a( L+ L7 }( W8 N
108 g1 f. u9 ^% w8 {5 j4 i
Example1:每月的第一天或者最后一天7 c9 U) C" o; a; L% q6 E

6 A% n" [: H, O. {) z4 ~  u3 \s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values+ K2 _: Y( ^4 X- i/ E
Out[50]: * X) h! b, F3 f4 I, v
2020-01-01    1: G4 K, w6 i4 z+ B2 `
2020-01-31    09 d/ O; O4 `, y
2020-02-01    1
9 m" @* C, ^+ w4 X1 w2020-02-29    1
' h& j( l# _7 m* o3 h* t3 z3 ]# u+ Q2020-03-01    0
2 y/ s! w2 V& H0 Vdtype: int32
. s( t6 g) n; d/ H+ ?. k15 X! p) {$ s& ^, e
2
( f- G) W2 k$ n3 A) I3
; _- w  ^8 E: I0 s+ z4) D- X! ]  N3 j
5
  v2 G2 V) c) D3 b# S+ v$ q0 W64 U2 G- T8 M; f9 R4 c
7
5 V+ k4 H0 }8 x* J& v89 C! i8 e$ s2 |5 g. D
Example2:双休日
# C* s/ G( I6 `7 K
) r! E3 n9 J% o- j2 vs[idx.dayofweek.isin([5,6]).values].head()
3 I6 B7 W8 \  {) w% x! pOut[51]:
3 t! c' r) Z& J# @5 Y6 c2020-01-04    1
* F+ n. \5 b7 a5 \  y; x- i" d0 Z" O2020-01-05    0  R8 |( y" W" c$ I7 G; ~# t
2020-01-11    0
- P/ n/ A9 R1 {2020-01-12    1
. I% P  T( F4 \* M9 F& \# {2020-01-18    1/ T" L* q, T- ?2 H( n1 k
dtype: int32" Q( v$ f) b8 \. a, P6 R" s5 o( x
1, l7 `+ x. [- `. z- O+ l
2
1 |9 {/ M' n! J3 ?( D4 f3  v2 O3 [0 R# h: u
46 I; i/ b( y" t; k. w. S
5
( Q& R. z; v$ [9 M. `6
# [/ l" D) S* Y) n' S" l75 a: e4 L. T" A( b+ Y$ W6 U: V) M
8
1 s+ }1 p( }( w; K( X" K- [Example3:取出单日值
  ?2 C9 d, @/ c
3 T8 U5 A* p) V' ~7 A; A+ p9 ps['2020-01-01']
3 R/ e7 y6 {9 e3 x  xOut[52]: 1
! h* S: K! Z' @# x0 R+ O) Z% q: z& Z- D& f5 n+ Z& R7 J
s['20200101'] # 自动转换标准格式
- g: m9 ?1 M/ [3 ^  d5 H4 b: [Out[53]: 1
, k* J. t' I* |6 c' {/ l7 b17 J5 u& }; ]" u4 ?6 g! M! J
2& C0 G. Z: ^0 c, e# m6 X1 c& T
35 x$ y/ M, O8 o5 m% s( @! Z
4
4 J' B! {- A; Z5 }, H. u* a7 |: ]- M5
0 s3 a# ?. [* u& ~9 rExample4:取出七月  f  V3 A- c7 R& L
; P4 }4 q6 E& `- U
s['2020-07'].head()# ~" u# |: H/ W' p4 m+ p
Out[54]: 5 \  L6 X  ^* o# {; \5 v3 D
2020-07-01    0# \; K  G8 w( K( U
2020-07-02    1
3 ?, G' r. b& c! l8 G; J0 x. z2020-07-03    0
; z+ t; l1 Q( @+ f& c* l, a2020-07-04    0
% q5 F8 Z2 z* M& d( N2020-07-05    0
4 v1 e* R" j' I; i) N- A0 K) GFreq: D, dtype: int32" C% j1 U# Z! h% I
1
3 b, ]5 [* @* e" N) N- y2  t3 s$ ^9 G7 v" e0 V- m
3
9 U2 J/ y& [; F( o# b3 T, y4
/ `6 E3 _6 T+ O: T56 A! s7 ]5 i, Q! a
6
% d0 @" S2 Q- Z7 ~1 v3 C/ Z7
2 p1 D% K3 {: {# d7 A2 s8
4 v. _" p- I* vExample5:取出5月初至7月15日
9 y6 r6 m( a! o8 E1 `, d3 Z2 W" z& X( X" ^, T
s['2020-05':'2020-7-15'].head()4 F4 L5 v2 b. l: `
Out[55]:   l2 u$ J& v, v. E- [
2020-05-01    0
3 h* |$ ?8 C; ?2020-05-02    11 x9 Q( n( o  [& n8 e
2020-05-03    0. o0 [' M% B$ E( z0 n
2020-05-04    1( M$ H7 W, G6 l: r, q" u  P
2020-05-05    1
6 Z/ _% L+ _: A# e9 bFreq: D, dtype: int32* ?+ P* r- u4 ]( c1 Y6 ^
% e  X1 Y5 Y, Y  Z: T
s['2020-05':'2020-7-15'].tail()) @6 O$ z4 z! o$ G0 Z+ A: q/ @
Out[56]:
/ {$ {8 }8 {3 o2020-07-11    00 d. J1 L; S" l% R
2020-07-12    0
* k& k, M. M0 |  E- q2020-07-13    1
/ m! D0 a/ g3 F- J, S$ a, \2020-07-14    0( L) }( N: K) s8 ^7 r
2020-07-15    19 h( _+ P- k3 k1 Z" f
Freq: D, dtype: int32# i& Y# x: P; g6 {. J
0 }3 i7 q3 L' z: |* a- X
12 x+ I. _8 e. o, S. f
2
( W4 h. |% D7 Y. G3! i8 G5 f5 D! w6 t
4% @% h. w3 x! ^; e. T; E
5
; v/ O2 B9 ^  ]# y# ?7 u* Y6
7 Q# d( M: L. t/ |3 K9 @' m72 U; w( l0 J4 \) h% Y. @
8
0 a8 C+ Y( W0 A. |. T  N" b+ b9
$ T& i7 @% Z5 s) n109 _$ c3 g0 N5 R8 B5 Q$ s4 u
119 r  f/ k2 }/ ~# c7 K7 i# V
12: A7 p" n& ^2 X/ C4 Y
13
0 ~7 p4 }$ z) b4 V14
6 j2 v0 }! Z% Y15
7 P0 g" P! r8 m16; T: t. ~- E0 J/ ?" W+ U- ~
17
5 r& I* x/ f6 X& R6 r5 D10.3 时间差1 C: ?& J' q4 q# P3 j) I3 ]
10.3.1 Timedelta的生成
5 [- M1 x: {1 ^. w$ B$ rpandas.Timedelta(value=<object object>, unit=None, **kwargs)$ Y# C; b0 I; x
  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
" o6 _: T. `# ]& Y( ~. D  可能的值有:
$ M0 \8 p: w6 V& c/ U9 F1 K' f: m' z" A' |! h+ E2 l3 z* m: R
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
, X! u& _/ r! g3 J7 L0 {4 S" {‘days’ or ‘day’
3 O4 a/ @; U0 ~$ Z% o7 o1 s‘hours’, ‘hour’, ‘hr’, or ‘h’
: S9 Z* A" E3 |! w' }! u3 v$ B‘minutes’, ‘minute’, ‘min’, or ‘m’
$ r( V) g0 K: o$ k+ [! B‘seconds’, ‘second’, or ‘sec’9 [' g9 Y$ |8 W' S
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
; y8 Y, X" i& \/ c4 n- G# v, t微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’$ t4 i; g/ \  x0 k- h3 C& [' J0 u
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
4 h6 I0 T) s* V3 g( t: s: O- K, d$ a时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:5 R7 J+ E! W9 u4 o: g
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
4 W% _( W: _# H% r4 hOut[57]: Timedelta('1 days 00:25:00')
, _. D" f% K) h! n0 ?+ T7 i1 H% T& }7 G4 n/ W
pd.Timedelta(days=1, minutes=25) # 需要注意加s
- R6 I: s$ ^  I: v5 C  ^4 @* IOut[58]: Timedelta('1 days 00:25:00')" d) w  w8 D' `* L" F0 O3 s* ^

1 R( x+ B+ {( I& kpd.Timedelta('1 days 25 minutes') # 字符串生成8 a& Y, V/ Z* q# J1 h
Out[59]: Timedelta('1 days 00:25:00')
9 T- B2 s. [" i: P% O( p% B7 ?' s% v# E6 y8 s; a/ ^* a; `. J
pd.Timedelta(1, "d")0 _# [! |& @& D' |) w5 n4 m
Out[58]: Timedelta('1 days 00:00:00'). T; K4 p- L% B! c. C8 z
1
; R8 m6 F, f. n6 ]* [' X2; D. B; s. n8 u
39 Y9 R  g" ]! v; D! c
4
9 S: }9 L9 _+ w  e5) ]8 A- R( G, F/ w8 m
6
" Y& \- n1 i4 A" x4 {$ m1 {7
8 A$ g; h1 G4 w: [6 R8
9 w) T0 m  l4 {, |* R* S) z0 n/ I9$ C6 R1 U2 J  L
10
; q8 \3 @+ X$ w+ }0 k3 T8 L11
2 Z1 P! H9 b3 K0 W0 m8 a- b生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
1 f4 U6 R; G0 P! Xs = pd.to_timedelta(df.Time_Record)
& K  x& L- `+ n# {/ }# m
; Y$ [% p2 S& m: V, ns.head()
& F# o( M) ?1 w4 [5 s7 }1 M# c/ [Out[61]: 4 }3 A5 a1 [, Z2 l
0   0 days 00:04:34
; p( V# M! W6 {& ]+ [0 Q  Q1   0 days 00:04:20
0 P. n( I: j3 A3 A2   0 days 00:05:22
' c4 e0 q2 A- @" c- ~/ n3   0 days 00:04:086 Y( Q$ i# b* W6 ?! n1 c2 j3 u
4   0 days 00:05:22) g5 K5 D! A% m0 q/ Y9 i7 F+ b" G
Name: Time_Record, dtype: timedelta64[ns]9 o* Y( f4 X2 J% m
1
6 D# x+ S! {8 L28 ~2 [7 K" E0 I. z3 w. F3 t
3
" M6 Q6 Z/ t0 y& |/ |* l4$ ?% S& o& g$ H% g5 Y
5: K; ]6 d. t4 S6 d" g: I% @  T
67 w6 b# I# X8 x5 Q3 g* J
7
2 x6 Y5 F) h5 C8% a3 ?" P( q. c2 u7 @( Z9 z
9
$ M( f0 \, G2 L( l3 E5 `* Y* b' E10' h8 J% M; f# l
与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:7 D& g& e+ t' N/ ^4 T
pd.timedelta_range('0s', '1000s', freq='6min')
5 k; W: J" L0 Y# O8 \3 j& ~Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
& g& s  F  w$ Q2 U
  O9 @# a! N' q) M- p% Hpd.timedelta_range('0s', '1000s', periods=3)
4 X/ E2 L- e4 v6 ROut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
  b8 U2 ?: m& N( D7 ]+ F1
5 y: C$ X) r) ]- Z7 Z% s" S2
$ c; Y7 w5 q3 F% N35 p* ~% |7 W4 O
4
% b& `/ w: d2 f! z; o5
# A/ R8 z! s0 M- [对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:' D3 D1 E/ R8 L: k7 {1 G* E
s.dt.seconds.head()
! s) W5 M: y. j% _Out[64]: ( P8 G  z( `9 f& N4 {& |; q) H: ?
0    274( [2 @1 O2 _- @
1    260% @: _- J) e) U& x# t
2    322
8 Y  \6 `6 j% S/ h$ u( i  ^3    248
. z1 x" X7 O$ ]  K: a5 W4    322$ x& g  x0 t3 J# j6 v) ^
Name: Time_Record, dtype: int64: n" H& I6 z1 j& H* b3 v
1
1 E* `0 Q. i7 D  x2
# ?. W8 d, M5 _2 Q$ X1 z8 G3- A3 z% b# y1 W! P+ Q9 ^% X
4. a5 m9 z9 T& t
5
2 U- h/ o2 D% W" C# e) `4 q64 J. v+ Z, e2 G1 a- J
75 [7 P. J1 v- z5 q3 v$ u
8( j0 H1 U. J1 X5 N* X
如果不想对天数取余而直接对应秒数,可以使用total_seconds
6 @) e3 m* ^3 M7 J6 y# m$ W
% h9 t5 v2 Z) j# n2 T& C' Ns.dt.total_seconds().head()3 j1 R# H$ j2 z8 a& x
Out[65]:
# r1 f) d8 s9 }! ~0 y- [( T0    274.0( c; @% V7 n2 G
1    260.0
( e# @$ o: ^6 I. C$ B' a( I2    322.02 F* A% N1 T) m6 [" s! `
3    248.07 u6 O$ F! N- Z# O, E' o
4    322.08 |# R  s; r: t. l( `
Name: Time_Record, dtype: float64
* w4 A: q; l/ Z( ?2 E; Z7 q# f1/ z( Y# w- A5 {) g& S# |
23 L9 ?5 d  T6 j) W$ B, s3 a
3; i" e  e5 W; q. R% J5 ]. w$ K
49 ]* S9 Z2 G3 L8 x- S
5, q* h- R- v2 U+ H
6
8 C7 G( g. d( ?9 V+ s& G7
% w  R" y1 J5 ?5 g* {: }" G$ Y8) ~& I* d% V- R* M0 Q3 ]
与时间戳序列类似,取整函数也是可以在dt对象上使用的:
  E+ a/ Z& ~  v' ~4 |5 j, ?1 F3 H: f. J
pd.to_timedelta(df.Time_Record).dt.round('min').head(), P8 H( t, d- g* Y& l- E
Out[66]: ! n; F+ A' D8 e0 k  k! `* J' y
0   0 days 00:05:00
: _! t/ }6 r5 L* J5 p: o3 t% W1   0 days 00:04:007 M2 N! l  q$ Z; h
2   0 days 00:05:00
+ w% c& K) l9 [+ k7 Z: V+ h3   0 days 00:04:00
' Z9 @5 [' M5 }+ g  w5 z4   0 days 00:05:00
2 ~, a" ]$ Q* r& RName: Time_Record, dtype: timedelta64[ns]
% {6 p( P. [( v7 ~- }1
7 J! A" L3 }) S! F, ^" @0 g2
8 I* x  b+ y3 T- I7 y( U  Z3+ g7 c  L2 `# H1 w; G% x9 Z" V
46 F& G( a7 v3 v
5* x, j/ Z+ t( M9 m
6
; P2 p  x" E( @- W4 _! e3 g- C77 o* D- r+ X; p  ^2 G" T9 J
8$ z* ?; t& g8 F  _0 C
10.2.2 Timedelta的运算8 z% K( S4 \" e* [  ^
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
% y7 h  o1 a6 X2 C8 R( ]* Atd1 = pd.Timedelta(days=1). `0 k/ x2 B$ o; ~3 J# W/ S1 S6 I  X
td2 = pd.Timedelta(days=3)9 I  q+ w% l# A; {' o& g! F9 j/ S
ts = pd.Timestamp('20200101')
$ z" x3 J$ Y. a" s, [
, {9 P, T% q5 B. z, dtd1 * 2; c1 ?0 u( p( m' ^1 w
Out[70]: Timedelta('2 days 00:00:00')
& S, g- v& K2 H2 S) T" l& x( ^1 [( M+ Z
td2 - td1- X% U2 x  x$ D1 F3 z* O# Z
Out[71]: Timedelta('2 days 00:00:00')) B1 d% E4 w4 L

% Y- c) U6 \% ?ts + td1$ f! o( b2 v: i5 Y# J' M, }5 R
Out[72]: Timestamp('2020-01-02 00:00:00')5 C+ l' H3 {) A4 j7 o
2 G, O4 c6 ?+ v8 C0 Y2 c
ts - td1
  g( i; l0 s1 {+ `/ yOut[73]: Timestamp('2019-12-31 00:00:00')2 p+ T1 _8 o9 o0 o3 Z
1. P7 C+ i+ N# M- H7 }* b0 k
22 b" P0 r8 m$ D7 L& p7 E% A5 |0 X8 b
3! P8 _4 `0 j1 q9 x4 d) o. X7 O2 n' c# x
4+ t% P9 m1 @' N4 F$ z, R5 C6 V
5  N5 w$ Y1 _1 [* W; h3 P* X1 o
6
* y2 y& n+ e; E2 A7 q4 A7
' }7 q' H! w; u1 i8 W# b+ i* M82 I+ J2 S# b/ \+ l8 [* B# c
9
" @: ^8 \0 j# y- B! p& D* A106 U0 B% p; Z1 `2 I. p/ e7 P
11
5 m) l  N/ x6 w7 g  \12) m4 }! d4 o: I" L& y4 J
130 @; J: ^* \% \3 l5 m! t. ?$ b; u
14& r. C& C& H7 t- }( I" t
15
( a7 d* l# w0 {7 Q9 q4 Q) _时间差的序列的运算,和上面方法相同:
% v0 c( j8 T0 R- Ptd1 = pd.timedelta_range(start='1 days', periods=5)
- u. C! C' d  U- g. d: Otd2 = pd.timedelta_range(start='12 hours',
( q2 ~5 m, h( \                         freq='2H',
6 O$ R$ p: R  A) H                         periods=5)
, ^3 t* V  p8 [+ {ts = pd.date_range('20200101', '20200105')& I# r: i3 k; `; a* r
td1,td2,ts# d# G; y7 b* g8 Z

1 R2 Y" q/ I9 T/ }- nTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
* i! s1 d) l# C+ Z4 c& jTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',$ H& w% D) x* m/ h1 M; `
                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
2 [" d/ S) O; ]DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
8 u7 N( |# c- G" u% Q0 Z# C               '2020-01-05'],! C& y' F# x' @6 t
              dtype='datetime64[ns]', freq='D')* M0 Q% c- p# d1 r
1
; z$ B; u" P) l( v* W+ o2
6 |7 _4 o/ o" n1 {6 R) j) L3
& C( ~% A0 b: X' ^; J7 Z, |% ^0 _0 Y0 y4
% B1 I3 b+ N3 r" m# S2 E. [( J  R5 ?5
/ |0 x1 x- z  h4 P3 w* `9 N6
& Y7 E8 Z/ c* Y" o7
) w2 }0 N0 K; w8 M. T) x6 Z  Y! A5 G3 {8: K7 W) V% F$ h; \4 _
9$ V+ n$ Y5 _  t0 m9 |5 Z+ W" J
10
1 a  I9 m" W, W0 P5 l11' }) A- B* W& E( R' s% K& X
12
( c* r8 F9 i4 m7 U! ^- M13
  c. o, d9 L$ ?; N6 E/ Std1 * 5
: r5 |6 p; I( wOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')2 Q6 O5 f( w- h8 B! A4 X  j! E; A+ j

4 R3 L! y. Z4 u1 T( Q$ u0 G$ btd1 * pd.Series(list(range(5))) # 逐个相乘  |( c- z  p3 l/ _* r" J* W; l
Out[78]:
3 e: F0 B: m' e  y5 r0    0 days
+ ^+ ^. Y& F; u8 Y7 A/ l; G7 i1    2 days
  b, |7 ~  }. k# b* R2    6 days% K7 w/ Q5 ^5 V: P
3   12 days
: J, J$ n+ Y! R7 ~' R; l$ n4   20 days
7 c4 X+ M- n# v1 K% Q  T7 o& M& ydtype: timedelta64[ns]
/ o9 o% G+ m1 f( w" y
* Y! S2 T! ]: d& L6 }5 M4 a6 Atd1 - td23 j- r4 _; }- S  G+ j
Out[79]: 5 y& {. H1 h! }% Z! `, V$ h) M
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',+ z2 X8 {8 _! g+ [6 e# n- {
                '3 days 06:00:00', '4 days 04:00:00'],
3 u  u, M0 b; T* ?2 r0 G1 G  p1 X               dtype='timedelta64[ns]', freq=None)0 `# `7 W$ C* X' b7 ^% o3 ?4 a
. ^$ R* p& i3 M# m9 K8 e
td1 + pd.Timestamp('20200101')8 u  o5 }) A, [
Out[80]:
9 w3 |) S$ b+ P+ P$ Z6 P! o: o/ NDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
/ g8 N4 p6 Y1 m6 {8 a5 b% R               '2020-01-06'],dtype='datetime64[ns]', freq='D')
! c5 |" u1 X8 l  x0 Q# J9 ^
3 @, Z5 `" Y8 X' Vtd1 + ts # 逐个相加
% o. z- a  h# e. I% ~Out[81]:
1 o8 q0 @  S7 EDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',: B7 R  e5 R: h
               '2020-01-10'],
$ N3 H7 N6 v3 H( Q3 ?* ~2 ?! I              dtype='datetime64[ns]', freq=None)
$ e( [6 _  P# j( w
  j& Z5 h5 K: R0 [2 I1 g- K1
' r* T8 {/ {4 D5 n  X5 T2" @: K$ v. A. r% d! H
3) ?. S' h8 @) d: T
4
1 e2 r  e9 f( }$ b* s% E& d; Y. h5
4 z" U+ v8 p9 R& c6# ]& `0 L$ F- s5 ~  y# e
7
; \) p' R7 Q) a86 T5 |. R- T+ m
9
. P+ Q, c) b8 @' [6 ]2 U: u% ~/ I10
2 ]5 j& u5 W3 J11' y' D8 w7 U* b* S! N
12, z* H! W: T( n/ T6 R0 Z, t
13
  p9 @( J$ t/ ^4 \148 E) i7 M" m$ e7 j+ k/ w
15" U0 v- l7 A' \- n" U
16
8 v( c" N5 Z/ Q& F; b3 Z4 n& r1 a170 B) t' z4 X8 h1 O4 A. q3 x
18
  X& d8 P" f- P3 y% @* H/ ]# A% X/ @19
  x- B$ [3 l$ s1 X$ g% {) B201 M/ L2 }0 J! N  T( B2 `2 W8 e
21
. P4 U; ~. N+ E# [  D22
7 l0 S7 ]5 c2 g' [/ t# H. g& L23/ H- T. X8 J( j$ B7 k) s
24
- e, ]: J# b% y) D25+ G4 J' G  j: c$ D& E9 {7 K3 F
26
- w7 k; |* L; }( t2 ^* f$ z27
1 B, x4 Z/ z9 R' n- ~28/ w+ d* r7 ]: P) B- `0 I
10.4 日期偏置
9 @6 l$ p3 H, ~10.4.1 Offset对象" M8 l* `' D4 ~% B
  日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
4 ?1 v. F9 d, s$ M. }$ e: |0 j  J3 |3 e3 J7 M% A- \( s1 I
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:9 }+ \6 b- w. r7 Y! \

9 `* l$ m( s; zs.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本; l9 ]' \4 [! s' f" ]
s.kwds:{‘week’: 0, ‘weekday’: 0}
4 ?# [) m% a3 V4 v7 |s.wek/s.weekday:顾名思义8 _& o% k/ s* z: w1 Z+ d
有14个方法,包括:" ~, e, i4 Q  f+ o- U
0 R  V1 |9 a6 c
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
* n9 @" d. {9 d' j' |2 Mpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。4 ~; y: S) h/ C" @
( ]5 P8 n3 s7 |
有两个参数:( A4 ~1 a/ w3 p1 {* _
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。2 J, J  `4 t1 I. D4 u2 `; A3 [2 k
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
: X5 l0 \3 n% a8 g  lpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
" n/ l/ F+ i. \; N, `2 Z" C9 ~/ o: i8 `4 V
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
- L+ M$ w8 [8 |1 NOut[82]: Timestamp('2020-09-07 00:00:00')- t1 A: O+ x1 z

" N3 B" y% F- z3 |pd.Timestamp('20200907') + pd.offsets.BDay(30)2 E+ M7 m. `' l7 ~, I5 R% p4 ~# j# `
Out[83]: Timestamp('2020-10-19 00:00:00')
! x$ o1 @& H( C; u/ M! r! b1
) A3 a+ K0 B2 p- a5 F) o2
+ `$ x* t: ~8 ]1 Q* k9 v3
: t" G  }3 R' _9 O' T4
. B. V6 Z8 J) {* H( J& d5$ q* o, h; w, `% S8 H* ?- D( ?) Z
  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
- l: Y) ?$ h! e3 \' E. @
- t* i4 M% f- M" Y- R9 j! R8 \pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)( e5 [. i+ ?& c0 ^% }
Out[84]: Timestamp('2020-08-03 00:00:00')
& G  v$ c/ ?0 T( f$ e! N9 C0 S2 I: @
pd.Timestamp('20200907') - pd.offsets.BDay(30)
# A6 Y) ^% @: T/ D' `Out[85]: Timestamp('2020-07-27 00:00:00')
( J$ w  u1 j8 r- X+ r' ]$ {1 T3 ]- R, @; o* E$ z. _+ t
pd.Timestamp('20200907') + pd.offsets.MonthEnd(): @2 J) o" c# N) W* ?0 Q6 l' ~
Out[86]: Timestamp('2020-09-30 00:00:00')
5 v) L) f2 p+ Q+ ^" h  [$ Y, M1( x0 E  y0 R- e( @
2
9 e$ h6 {; v) [8 ~% R/ m) A( P2 \3, q/ i" i7 z. T# F; G* m9 j- C
4
/ U  r8 J( c/ z# M5( c( ~- F2 Q/ ^- z5 p' W/ T
6
* S% h( u" g  q; S/ _) e7, k2 g. I6 k. U, I- w8 E/ m
85 y$ m5 W  Q/ g  ?* D* l
  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。; [6 \# ]2 ~) K% y
  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:; z$ Q% b6 e1 J* M7 D) `* `
+ ~0 r5 n$ N  y! M3 w4 P) @' v
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])% K9 R  D6 k9 N# p) r2 U& z
dr = pd.date_range('20200108', '20200111')
/ I* B9 b( U4 i* ^+ l
; h+ b1 l8 `! A0 _1 Z0 Ldr.to_series().dt.dayofweek
" h! |) ?" s7 R6 U2 xOut[89]:
- X4 Z2 {& |& m; ~4 H3 }! @: ]2020-01-08    2
( ?+ U% f% s- u) x- R$ X% y2020-01-09    3
- ^1 l' F( {4 K2020-01-10    4
' k8 l. y7 R1 n- ?2020-01-11    5* t' H9 ~, D6 w$ ?5 {- U
Freq: D, dtype: int64" @2 I$ L) }- O* a9 T0 v5 f

# `! F, p7 @7 ^; S[i + my_filter for i in dr]
" b! h& u4 c7 r# rOut[90]: ! h. K. o1 {6 }+ [% Y5 d
[Timestamp('2020-01-10 00:00:00'),
5 `8 a( s0 d- J: ?5 t- C- J Timestamp('2020-01-10 00:00:00'),9 K4 ~* g$ }- W- P, @  n/ a9 \
Timestamp('2020-01-15 00:00:00'),
' C; G+ [$ x$ M0 t+ B Timestamp('2020-01-15 00:00:00')]
5 I: I* b: N  ^2 s9 d3 [- @6 T. N' _( X3 E2 F9 i
1
2 W8 m9 C( Z7 Z/ k/ K1 t2
: X9 p3 `0 ?. g! n: B1 _$ K3/ D- s) r( K9 ^/ y/ |* Y
4
/ h0 J1 G4 u- \  T" h5* q% B4 Y) V  A3 a" n" A
6. w, Q" l1 k5 E. `$ }9 U, M
7
" K# k' I4 C2 N- h/ M81 a) F4 t+ p0 |9 l! B1 p5 f
9
" M" d- h8 w+ {" l# u10
9 w- \+ S; j! b8 \" v2 ]11
2 `- {5 f% S5 H3 v" v12" D( B$ O; ?1 X" \8 _
13! t7 p9 ^3 E2 T
144 J4 E' f$ U0 ^' @3 c
15
9 G& y4 w! h" {/ Q# e6 l16; c& a; `6 D+ q2 \& Z% w: c
179 f1 S/ _" T6 }% j) P' O
  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。: P. ~( {9 H- k! ]) H

# g$ |0 U+ P. ]- M# H【CAUTION】不要使用部分Offset
) M+ K& c+ w, G* N0 ~' Z  M! B在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。9 _& K  B# |$ P+ x) a& H

. ^# A  Z* v: V2 m. R10.4.2 偏置字符串6 w+ V! h0 L+ q: A4 H& N
  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。  z! e0 I9 c# D4 s9 @& x9 `. r/ v
% z: P) F& l+ Y5 L! R( Q' ]1 @
  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
. `7 @( N3 {: h" C7 j/ m, F8 c; d: h$ j- v; c. I
pd.date_range('20200101','20200331', freq='MS') # 月初, j: \' X4 v) D6 J2 u. {" q% Z! G4 M
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
' M- J: [2 W3 V" ~! T+ h5 l' F4 [( ~" {! J% A  i' h! V& W5 t
pd.date_range('20200101','20200331', freq='M') # 月末- f: M; {, y, d/ p$ O2 v
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
) W) D% K$ H* Z" J/ i- L3 I
% p% h8 t+ z; \9 G3 S$ }1 S) Q( Epd.date_range('20200101','20200110', freq='B') # 工作日7 l: l+ T5 S% W
Out[93]: 0 u% r, u9 @3 G9 W; {
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',/ {; z# S' U# Y
               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],# P: g" }: c- g( S* T
              dtype='datetime64[ns]', freq='B')0 D" a+ u  }* k! v! G
# _; W/ n" R8 U/ k
pd.date_range('20200101','20200201', freq='W-MON') # 周一  _1 z' S& k  Y3 t- w0 |# y+ b+ L
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
* F: l! M$ ?" ~; E0 y8 [4 G  l9 O/ P: x8 e7 ^$ g9 Y
pd.date_range('20200101','20200201',$ d  J8 K2 X6 x7 ^2 n2 [) g+ K, `
              freq='WOM-1MON') # 每月第一个周一8 G5 P6 r7 p: `7 S% U( s
! e" l+ D; O2 X4 [/ L. Y
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
$ X/ T* w' X' Z3 N' Z$ @
! h* z0 w5 [9 ]. j  Z) ^% Z1. Z1 f3 r0 _5 O  F; s# L; `
2" [7 \& P( g2 w: h. L. H
3
% L. y8 P5 O/ h5 Y' {4
9 S2 Q4 q/ Y- q( n& l5
/ B5 p6 `  U( F6
) Z7 L2 ^+ {$ G" G7
# I; H; H! }) X2 u0 [& z8 g8
4 c( N* M! m3 D+ w9
, p7 b5 `- P; S& k( |" _10! H$ Y& ], W/ W- I
11) o) J( c2 I- f# `7 H' u
12$ W! Q; T- \4 D# e% E- w
13
) n5 A3 o5 O. U1 X14
0 s; V: [& M4 Y$ U/ x9 B' s% {% P15. x/ T) R% u5 T. h; w! ~
164 |# f* a( r$ O% F
17
- k# I. N6 L; v0 b( h18
! ]" X: K' Z* U! v, Z* s! K' I19
$ Z, ?+ a+ C* ?% ]3 c7 A上面的这些字符串,等价于使用如下的 Offset 对象:9 j( d& y% R" [. H" t

( {( `- o7 `. x" npd.date_range('20200101','20200331',
1 R( C. Q0 _# y' v; U# P; x              freq=pd.offsets.MonthBegin())! a) D6 ^* Z1 |0 U3 P! f
5 l# M* c) k" K! U6 L, E# ?
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
' G9 O; q# V6 F/ X. J' t4 V
* w  w2 K( \0 ~/ T: vpd.date_range('20200101','20200331',
- c/ M4 E8 ~, [* J              freq=pd.offsets.MonthEnd())
. v% k# M& O9 w8 Q; Z. D; C9 k* m, J" K
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')1 A0 p# M$ M, u8 q% ~9 s2 D

* u) j% m' i2 Y, u1 bpd.date_range('20200101','20200110', freq=pd.offsets.BDay()), `/ j. o; {$ u' E6 d$ }/ m
Out[98]: 0 u5 T2 z" e  w" J
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',& ?( K: Q, a  S; \# g, R1 q( f
               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],8 h% E$ [+ l2 L# g8 {; n
              dtype='datetime64[ns]', freq='B')5 A* A( ]& g7 k& V" j% g
8 k; N% O: Q* M# X' S- v
pd.date_range('20200101','20200201',' J- ]1 D8 `+ V% h" l
              freq=pd.offsets.CDay(weekmask='Mon'))
* w% b, f0 ?9 h( Q5 P! S! }# e: H' x8 D' g9 z
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
4 [- V  z7 M" u6 o1 [( B, M
: `: U7 d3 F; a* T+ L9 r1 A5 Xpd.date_range('20200101','20200201',0 Z7 |2 x) @; b5 [
              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
5 Q/ V$ R9 o% k  k" I9 T/ R. i1 y6 f5 f6 x3 L6 @
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
' I) W* S, j8 c& h1 b# Y9 t' I$ j6 ?  ]' q0 z/ c: R; u
1
7 [4 S% O  ^9 F) {$ ^2- k7 O9 R8 @" l- l
36 f2 d# w" W) `9 x7 _
4% p# Z! [0 A2 R" I6 z# S
5
+ y3 v  F7 Q( N1 j/ {& Z3 O6) G. b* f8 {! g  V$ z
7, ?" k7 P5 U1 z
8/ ?- ]- r5 ]; k: X+ L
9  M, M1 c# N% _" ^+ F8 ]) W
10
5 T0 Q7 R* D7 F8 Z+ I& @3 y11  x: M/ M% t7 m% c
12- `9 x' K! w; U" i: F
136 B$ q. f3 q1 S- ]
14
3 x6 y# f( L+ U/ x) m, V. J8 G15! S: b8 V6 `" A
16% `+ q8 c2 A8 A) g8 M
175 u, O$ O$ ?, z, |/ W" h( w5 W$ D1 I6 u
185 H/ _' Q4 p0 i
19" B/ ^6 w4 s+ S7 [
20
- ^. n) S2 J# v4 M+ F8 G7 V2 D1 ]' p21( S. M0 e3 z( G
22( Q3 J3 O1 R$ L
23
/ e+ ~1 t6 @8 f7 {0 j5 D* t& f24( P0 J( {2 V  n1 r% K
25
0 j3 M# d3 ]' Z8 }/ E: [3 n【CAUTION】关于时区问题的说明
/ K& Q8 v7 q! _- W) T, ~  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
5 S" l  X) v$ Z9 Y& R  C. @
* A2 U( D% R4 d6 Y' j6 o10.5、时序中的滑窗与分组
4 k1 A2 X; E# H! \& `9 _10.5.1 滑动窗口' ]# w8 R9 @& t5 h# k/ @6 u
  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
/ d/ C3 b: |; C* @( q. T
, [6 h3 V' L: k+ dimport matplotlib.pyplot as plt, H$ E0 I0 P! I. O; d7 J# J' D
idx = pd.date_range('20200101', '20201231', freq='B')
/ f  _- Q1 E( `, t  Q; O9 `( {. @$ Pnp.random.seed(2020)' s: ]+ ?) }1 \( P
/ W7 C8 c2 E8 }: a, W( P, h
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加$ f+ C# g0 J& j6 u
s = pd.Series(data,index=idx)9 e. S- a; j" l8 t
s.head()
$ _7 Y$ B# E: DOut[106]: 9 @4 U& ^& H! y& C
2020-01-01   -10 \/ v) L# @, h4 f( X: o0 i: y# o
2020-01-02   -27 I" a0 P! S. g" X
2020-01-03   -1
+ i1 c3 l* i: W) \9 D" n5 G2020-01-06   -11 t# a) E+ I7 L# x) j
2020-01-07   -22 c/ [9 N5 d# i/ `, D4 W- h
Freq: B, dtype: int321 h: V% {$ x6 U4 p4 n" W
r = s.rolling('30D')# rolling可以指定freq或者offset对象  I, ?+ O. S* E

( H- q) @, C1 Q+ {) V" Tplt.plot(s) # 蓝色线) a, [4 ^& m1 A7 @4 R- }; H/ u, n
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
* I( G( L, ?2 \- w6 M8 [plt.title('BOLL LINES')! D0 x! }1 _" Y8 o! ^) u
Out[109]: Text(0.5, 1.0, 'BOLL LINES')" ?0 a* W- ^+ p9 ~2 V
/ }$ {2 e' ?. k  c) H: k. s. {; y6 B
plt.plot(r.mean()) #橙色线5 j8 m, m% D/ z+ [" n1 W4 q: r
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
  t2 F! j1 `$ S3 b0 `2 S7 l
7 Z% C+ k0 u* U- Vplt.plot(r.mean()+r.std()*2) # 绿色线/ w. I$ D8 c0 o8 R' B
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
) p2 @1 j9 D* W
' z- L. K# b+ N' {* i! Fplt.plot(r.mean()-r.std()*2) # 红色线
$ l: S% C& [6 ~3 iOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]2 |, A, ~  C7 [/ ?
. Y: F& c" H- `# z4 K, t0 V4 ]/ H
17 w8 O& a: |* q: X+ L0 v1 H+ ~
2
1 x8 u! D/ b9 G8 M8 f37 |! c5 d+ V$ C1 P; T( Z* w
4) g+ ~6 E2 D. o; @. ]% a$ u
5
0 F8 d  y0 ~- v( n5 J( J7 U68 y2 b, {, s/ q  u- W; D6 x, P4 l9 C
7
/ I+ g+ i5 K- `/ W: u% r" A8
! Y$ K4 ?) l3 f0 `6 k/ v- D7 I9) u5 l; H0 y0 t. Y9 H; a% m
102 _0 Y+ E4 D* R$ L
11+ W3 Y* y3 p6 f# k  i5 Q* q
12
, Y! ?! {& B) f4 q3 a* g13
. K8 T! s& B" L143 W! H( d' ~& ]4 s
15
" b. t* p. [( w) K16
& s8 X" n6 ?' J' `2 Y17, i- b0 t) I! O. f# @" y
18! \; q% S% a$ n7 a' p
19  U/ i4 s) G  t
20- v3 U- W& |( T3 ~. Z! t
21
) ~% ?. C$ T' v/ V0 A5 t4 n22
( F- E2 O8 i5 k. K/ G1 b1 y23
6 y) c: C5 O" B8 M3 |5 i248 B( E+ U% \% n  _& R1 Z
25
* f9 T7 x0 M) ?- C% \26# C# X. B. {( Y5 V- m
27! ~7 C. X( K9 q7 C0 ~
28
6 ~. O, v4 v5 i& T. u29
% _" I# b- p+ R& W5 \
" b6 X1 F3 @+ a( W/ M0 B0 k" k0 I( t   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
/ _2 |1 l6 Z, y, A6 V7 q   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
; _4 P* t; k& B8 i) {( I
5 `0 S: \" z7 I& i9 F( ~select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
' [2 C; |: B& y% I) o8 ebday_sum=select_bday.rolling(7,min_periods=1).sum()' [9 u$ w1 i1 v! G1 U+ f4 X
result=bday_sum.reindex().ffill(). R5 f, \% s+ ]- U: N7 o* f
result* h. V7 U: B3 s/ p

! O4 i3 Q! C. E$ L2020-01-01     -1.0& p, ~. K/ B/ R5 V: j0 f2 }3 {
2020-01-02     -3.0+ Q* }2 d% L+ y: n- S! ?( |
2020-01-03     -4.03 b, I$ t3 }: v' s
2020-01-06     -5.0/ i. c% q$ @/ N0 E- i  B4 w; e
2020-01-07     -7.06 q7 o, d3 I. w& G
              ...  
8 b* D+ m' ]" G9 U2020-12-25    136.07 G4 P' K+ g( P0 p* M
2020-12-28    133.0) h5 S& `+ \0 O' J( E
2020-12-29    131.0' w7 b2 ?+ I' Q
2020-12-30    130.09 ^& @$ S1 o/ w0 Y3 X+ U5 y5 M
2020-12-31    128.05 m' l" I. \  }# P# p/ d; J
Freq: B, Length: 262, dtype: float64
) j" a' r! v; m$ g8 [& `; J: M; I8 S/ ]6 C9 K  i" Z% j
1
5 G0 Q* }8 e2 x$ q* f1 Y2
$ B# G( G8 ]; k( n' I0 ]8 l3
% k; |( Q  w7 B4
) M9 w) W4 @# x, f58 F6 O1 {* t  o7 A- o
6) v- m$ C/ D. k* r/ k6 d" c3 H* e
7
' R) h* J8 y8 t  y& {5 v8, y! A7 p6 O7 M
9
( I6 g+ w" `& L& P$ l10
& H* w5 R) H! T/ V8 [: W+ c11# K1 w  g* I) X) C" z9 o6 ?
12+ Q9 z0 {. a' Y8 r
13, E2 a' @) V, A$ ^+ l$ O+ h
14; _. ~. L$ ]) i" o: U
15
8 l4 T1 i, T9 r( x8 o1 |8 u! N16
5 h8 K$ o4 }. K1 I2 `17
: z( i, {3 t$ J  x$ a  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
) V, e4 ?; q0 _* B9 t* ]6 L: ?2 c3 j+ _
9 f4 X1 f! L% M  v$ f  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
+ \: q& p# T" N; P% l' w1 y1 q. y
$ z# o# Z5 k) ~) l/ n. ss.shift(freq='50D').head()" n/ V4 Z" T& F" W
Out[113]: % g/ F( o, ~: H: ?+ {
2020-02-20   -14 h% _: T5 v* N) y  |
2020-02-21   -2
& Z; P5 K: E  V7 Y2020-02-22   -1. d& r8 C! M$ L5 m8 R
2020-02-25   -14 g9 h3 ~9 U  [" {8 i5 e2 i
2020-02-26   -2
# }  k% a- \4 E9 |dtype: int32* i! t! X8 `1 J7 k5 p% t
1
% j/ j: X( e- x5 H" x2; j* V) A# G9 Z, K! F5 O, V% Y0 Y
33 X& J; V8 H; ~4 J6 P0 a+ V* d
4
9 ^4 F6 I) ~/ ]0 T" H2 \) X* s5: P$ i; G* v7 E, I8 W4 s" R+ B
63 r: Y- c! _' P! Z  r, E
7
5 d# @; V; u" J1 v+ r& X4 u% Y8. D' |8 w8 }5 z2 y1 R
  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
  [2 r  V/ M: e  a  `7 v3 X" v
6 z6 I: E/ P8 J8 S( Jmy_series = pd.Series(s.index)  M. M& W4 O$ h# `! \
my_series.head()8 `( P" n, V0 N. ~
Out[115]:
6 Z9 v7 H3 u3 ]2 ?& ~0   2020-01-01
! P7 r! F# `1 |1 E0 z1   2020-01-02- g# n4 O. ~; V% a. P& q
2   2020-01-03: P# K8 N$ M' N. \* F( e! B
3   2020-01-06  _) f+ y" v1 l/ n! A6 a
4   2020-01-07/ Z9 V) t' f9 f! l: d
dtype: datetime64[ns]
+ v; ^# j9 |+ \- D
' q' m* }/ w. K9 m4 K9 T9 F! `my_series.diff(1).head()3 T" \& L$ a" ?
Out[116]:
* d, P7 t# z7 P# t$ K& j% n) ~6 M. t0      NaT; K' ?1 q' |  {, M, ~
1   1 days
5 j7 H4 X: g  J& c8 S) K6 n2   1 days- o  K2 D' D' G, j4 D  k+ M
3   3 days
% }0 c" O3 ^, l+ _$ x2 o4 ^# D4   1 days
0 x$ d/ t) f7 x! @dtype: timedelta64[ns]
6 U' x- M* ~2 v; e* _1 E
4 M- M' s* w6 a8 x1+ Y/ O, }- s) s
2
' C7 G3 B% q6 @" Q( A3 m3( i+ O  _2 _- U& t2 _4 _* j; Y
4" c# M- g- K2 ?, H+ a7 h
5
! D, B' @# s5 ~8 w: f( N) p6, t/ k" C8 t( l% F" L( z& @9 `5 \
7
6 B% g9 r2 E8 f, A' ^: g! Z8" n* N- N3 X8 i& f4 x  j. W5 R
9, y2 M; v- ?2 M6 T3 R8 C6 o
10
. }1 p. S$ u9 m, I% ?7 Y( ^& P/ t11
$ }, W" u$ ^+ D123 c7 g2 [8 U* \& ]) S5 d
13! N7 w* p# Q$ M, p, B- c  A. M
14
6 D' X% Y! T7 [. M. X  F9 r: h15
, n  S6 M9 o& d% t9 Y4 D; B) Q16: l% p; p: a4 X, c7 x
173 g( ]+ T( F7 e# w6 D! x
18
$ F* C* V7 I6 t' N3 Y, w10.5.2 重采样* s! F. X2 s( l; Y
  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)* |7 A& q  l. ~/ ~4 A
常用参数有:
- ~: q# }: J8 ~8 ~1 u( w8 j* n* L
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象5 ?0 y! V# Z5 ]
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
% k. r4 g) x8 C$ o9 v8 Z+ ]* U  n' U' aclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
- h* s. v+ C4 P; R- l; Ilabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。$ P' D" n# `- Q9 A" h* {
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
+ W# Z: o+ _# E8 z0 qon:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
1 A: F  K( f" K9 f& z( {3 Mlevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。' x5 T  h! J7 i; t2 t
origin参数有5种取值:3 S! M6 f+ B4 M: O, f
‘epoch’:从 1970-01-01开始算起$ c; u% y; ~% S
‘start’:原点是时间序列的第一个值5 i0 k1 I% l7 Y, s# x" }, N1 U2 ]
‘start_day’:默认值,表示原点是时间序列第一天的午夜。! H! L& C9 Z! ]. C7 S
'end':原点是时间序列的最后一个值(1.3.0版本才有)2 J# J* D. M3 ^8 ^- u5 z" k# w
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
. J1 t0 E" ?0 d& coffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。7 S; x2 X# U  Q; @. j2 a
  closed和计算有关,label和显示有关,closed才有开闭。- R. B9 t+ f8 A: W8 {
  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。  c8 M& |! U; u2 [
6 ~5 f7 L& P/ F$ D
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
$ z" g( s' T/ `5 J: Q$ xs.resample('10D').mean().head(); ?, m# z/ `. U' u8 Q+ H
Out[117]: * c- p0 i9 p. y7 `; n# g
2020-01-01   -2.000000
: k  M* d2 q9 P% {/ u& E' [2020-01-11   -3.1666674 M9 g7 r/ U6 W1 [# L. l
2020-01-21   -3.625000- C; A$ ~' a* J# R0 m, h
2020-01-31   -4.000000  i/ n+ ?% H" o5 H( X  S! w
2020-02-10   -0.375000
* ^; J4 S# {' l% TFreq: 10D, dtype: float649 Q7 T+ |. a# P
1
: S8 L+ c% |1 d2
3 A0 [5 P5 q: K5 o0 ~: b4 {4 x3& p9 I+ C  s4 u" P4 E% W3 x
4
% c2 s+ u$ ?7 x+ W: h- Z5( l# O# d8 ^% g5 ^5 {# f
6
2 ~# d) I7 f6 D. S' N$ \0 i( E79 k" R: q' A$ j2 Q# g9 `: s9 `
8
) b4 j8 g& }; ^, d7 n可以通过apply方法自定义处理函数:
5 K2 _( r. `) J1 w6 ~s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
- V8 F3 a3 Q0 O( O8 S  _0 p( R4 |
& q8 ~7 W. n6 Z' MOut[118]:
4 {9 a0 K  @% \) ?9 H9 @2020-01-01    3/ }! n5 A. l" v5 O
2020-01-11    4
# S% y% m$ {& K( ]& J; q5 t2020-01-21    41 d1 }. T9 n$ d9 i1 G' G
2020-01-31    2
: _4 X0 Y  j) S: @6 E& g+ ~2020-02-10    4
1 \5 K9 v+ J; _, _# w( FFreq: 10D, dtype: int32
$ j6 A) j* r) e2 g0 H8 d% A1  p- T5 T% h0 m' v' ]
2
1 k! v  l. ^3 x. b# S3 b3
- s3 t- z, p7 m! P- g' [7 D- }4
3 V2 Q6 h  O/ n$ A) `( R2 [! Z* u- t5
1 q# j$ E4 C- n5 P; m0 i6
3 L9 k! M# Q' m7
; i4 C2 F0 ~' n7 V* ]) o3 K, h8+ G3 `; m/ |; i$ C- ~" R
9
0 {3 U4 G7 e4 ^" W& A: b0 P  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
* V% b* J. H  P9 z/ a4 w! O9 g- x( F' J: i- J# J. K
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
# e2 X- Q. t/ }: g2 k. k, F+ _data = np.random.randint(-1,2,len(idx)).cumsum()$ }! K% R2 W( ]% v% c  I
s = pd.Series(data,index=idx)
5 Y4 h6 j4 q" J% E7 ?1 }s.head()
8 Y- c% Z7 N5 n1 ]9 Q6 N/ @- M$ n5 e% I
Out[122]: & G" N; U4 k% H& D1 p4 E7 k" t5 S3 L; E
2020-01-01 08:26:35   -1
0 X9 _% h- G3 d( g2 p5 |2020-01-01 08:27:52   -1
' M1 v2 C0 M" K3 U2020-01-01 08:29:09   -2! c2 m4 x" I, g0 D, ?/ C0 k3 D
2020-01-01 08:30:26   -3
# H* c$ c1 V1 {, Z; F- t/ b) B2 i2020-01-01 08:31:43   -4/ D0 I+ r  X. V# x3 j
Freq: 77S, dtype: int32
3 d  W9 f& }: x" X% f0 n4 \1
! t& D9 J: F' v! l$ p2, x# F( A  ?" U% V! k+ V
3
. r- K' I6 c+ A& b( k4/ `" e) E0 i. c) u) `( q
5
- S# g$ p: M) l% Y) y6! ]3 ]6 T: d" Z$ x; F' x
7
3 v% l$ K* b' I2 j# I% [0 N5 ^8  ]/ e! A! K: R
9
- c; v2 x# H' y7 d8 `3 A) P10
) B' P; c3 j$ S: E7 ~11
7 ^0 ?; {7 G% X7 A/ ?% S! E12
* O  H+ i" j5 t$ j. N( {9 d  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
5 \2 s) n( J+ I$ b1 k( ~/ R+ J2 D2 q+ W1 `5 G! B
s.resample('7min').mean().head()
' V* O3 y, u2 fOut[123]: : [! H/ b9 z: `% }4 m( |6 E
2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值: G8 k6 T& j- q* d
2020-01-01 08:31:00   -2.600000
7 U" c0 n4 f7 J2020-01-01 08:38:00   -2.1666678 J) C" h- ^3 L" s, s3 E
2020-01-01 08:45:00    0.200000( U. z% j! ^8 J  Y% ?% o7 Z4 W
2020-01-01 08:52:00    2.833333; x! {# V5 \3 {6 u4 e1 W0 B
Freq: 7T, dtype: float64
+ d7 e9 @2 d* C1
$ U2 J, l" t9 T- o* v2# `/ T, D4 h  O/ c
3
! V+ q- x+ K% v$ W4 X4: I$ i' v' U  b# @, H* n3 w& Z
5
# s' f$ S9 ]2 x  Q) n6. {5 o6 }' O" [9 I/ n  B1 X
7
' |4 G; m- ?* Z) P/ c( Z- l8/ @: S* j1 }" d' C
  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:" q# w! _! |" i% c# c1 e+ i

0 m( U! u; o: P% `s.resample('7min', origin='start').mean().head()2 V( h9 ]4 t& L& e0 w7 [9 x
Out[124]: , v: `+ ?$ h. q; r' {0 n6 C
2020-01-01 08:26:35   -2.333333
8 m* |7 y" z2 Z& |9 h# s2020-01-01 08:33:35   -2.400000/ G. r& y* G7 ^6 M) s
2020-01-01 08:40:35   -1.333333; d6 |) }; D1 N
2020-01-01 08:47:35    1.200000
$ d- @: p4 l& }; i9 e: ^2020-01-01 08:54:35    3.1666675 I: i  p; o7 @
Freq: 7T, dtype: float64
3 W. Q: f. a# m' w; P0 c1
0 C8 ?$ a  y9 m2' {- w9 f" Q; I' k# K. ]- i& v: G
37 w2 @5 y* |& o% h+ Q
4
* Q* B( j$ ^, j, R0 F5 e5
5 c1 D4 S6 y1 W. Q, N6
6 K# h$ m" u. ~/ ^( q$ R1 K79 U" ?! U/ a* x  I0 \" W
8& ~* Q% t- c9 Q
  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
. E3 W& g+ @9 O' |! ]
* r, S9 v. N4 u1 V( n  }8 M( Zs = pd.Series(np.random.randint(2,size=366),7 ?2 K: O: [, X7 h. f/ e+ {5 J2 Q: ]
              index=pd.date_range('2020-01-01',$ b2 ~' J3 o, I, L% Q
                                  '2020-12-31'))/ D& k. w! m; c2 {# E

( p" L3 ~3 Z  P6 H# @& N9 r- M  ^1 G' A
s.resample('M').mean().head()
) k% Z% |& j6 U6 J- ]2 n4 i8 cOut[126]:
- X! w+ h' X. G* M2 P) ]3 u2020-01-31    0.451613
, f9 R' E( q; S$ @; G2020-02-29    0.448276& o' V: }. S* `( G' [5 _2 V2 v# c) r
2020-03-31    0.516129
9 ~- V* q  m# Q) H% O% X+ @2020-04-30    0.566667
: D8 q  m& K7 N7 s2020-05-31    0.451613+ R; N: A" }6 {7 w
Freq: M, dtype: float64
1 h7 q0 k8 h/ |/ I! y( d" c8 i$ S! ~9 x! H
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样; [; t5 t6 a: T: O
Out[127]: ! M& [# Y) T/ p) S# u8 f4 V
2020-01-01    0.451613/ Z& N+ E1 l2 B% h& ~
2020-02-01    0.448276; G  [" z! ^! n! F; \3 k
2020-03-01    0.516129" S9 {2 F2 T8 i% f' @
2020-04-01    0.566667
3 j0 p3 ^, ~: |. v$ d3 a2020-05-01    0.451613
8 P8 A5 m1 K; v' w6 }2 A! |Freq: MS, dtype: float64
5 t( I4 Y$ P+ i0 J0 w$ e
1 D4 f3 {7 y: p. l; T- F/ k6 a1% L, Z* J1 ?. K
2+ Z8 E4 w- l% P; v( P
3
  u* @! U% J) z3 F4( ?" S1 F  Z. D8 Q% g# T
58 A* |& G4 O* R! [* ^% F& x# e
6
1 T% T$ S5 _) k6 \/ A6 s1 `7
) H& i" N( D9 h; J; P/ K' x# y7 a  Y8
9 D( K% R& n9 b& O6 C9$ r" U; a6 l+ i
10
( ?; E( R' h' ]8 P11+ ^) ~/ t- ]: ]' d
12
5 J: c4 l( u8 Y8 g13) U5 X/ q. S$ b) g) ~
14
- l5 p" @" ]! f6 Q15, r# y$ h' M5 L  l
168 y/ M3 m1 v$ c- D( E4 w
17
! x/ y6 U! ]) w18
& s6 ?2 q3 m1 r+ U19
! t: k: p: S0 s. Z7 ~% b204 s, _5 u/ @6 w# ]
21
4 w0 A4 n+ Y' S( @3 \* N22# n+ S& a0 R- ?" V4 n7 c
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
; ^( v! K0 ^3 d. ed = {'price': [10, 11, 9, 13, 14, 18, 17, 19],& ]4 O/ m" n" V  J0 J* F
     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}) i- [0 @# {/ y: ^7 C$ c) v' @. p
df = pd.DataFrame(d)
* E% w4 r) w+ ~+ sdf['week_starting'] = pd.date_range('01/01/2018',! x. y* y) b: N% y9 U( C
                                    periods=8,$ B$ {# B: M& D! |; X
                                    freq='W')" R9 J+ y  Y$ y! f+ b" s
df% |0 p8 F+ T0 B
   price  volume week_starting
, j1 q0 i$ Q2 U0     10      50    2018-01-07  R% E1 p5 |- v( s- B1 n8 ]8 R
1     11      60    2018-01-14
! v) i! r5 @9 A8 D7 M9 g2      9      40    2018-01-21
4 m" f1 o' V& M" _3     13     100    2018-01-28
; V8 G4 A" f# F6 G& M2 h4     14      50    2018-02-04
2 {9 _* T- D4 [& E8 U$ e' P5     18     100    2018-02-11
1 _7 H# E  u. M  M% a  x6     17      40    2018-02-18  B3 l- D( H( \; o* F- ^2 z
7     19      50    2018-02-25
: @) N3 |& o: |) pdf.resample('M', on='week_starting').mean()
/ b( ^" R0 a$ ^7 l               price  volume2 v( T3 B( L/ e& x) A
week_starting
. k; @; x& u: _2018-01-31     10.75    62.5
% j) Y2 n4 v4 @( B2 C4 j2018-02-28     17.00    60.0
2 `& G, {9 {, f; }% N* t6 _$ A$ f4 @: ?  K( Q8 d3 u
1
/ c. c+ @' @/ x  @2# N8 ~& x- B4 `
34 F) K$ m8 j# V; P/ D! d0 D
42 {- E% u! e$ o% i" Z$ m
5, z) m, @# C- N; d/ j" c1 N
6
. `" l) s. W2 S% D8 B8 \5 Q5 l7% Y4 f" l5 R7 ^/ @0 T4 d
8( I9 e# q4 W8 s+ |+ p: d+ k' L
9
: c1 N" Y' O# }0 E+ R10
( P5 o# \4 h: w2 d, S4 m0 x0 ^11' B, u, U3 u6 m- Z3 H) T
120 X' ?5 y* J+ O; A) r5 E: d5 r
133 {# e0 W+ m2 X& o* E; j. ^
143 H! k9 ]0 R6 l1 H% `; X& p4 I" S
15, u% I. F" E9 ?% e
16
; `0 j1 d' d3 {17
; l" D: G0 ~; j4 {. A" R18
' N0 j$ u0 ?5 r9 @- c" k$ q( o1 v19) i* I  y6 M' V( r: ?
20* U  v3 ~1 b: w
216 G+ T; {" i% W; X
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。: ~& F& b+ `1 F" \2 ~$ t: \, W* g
days = pd.date_range('1/1/2000', periods=4, freq='D'). ]3 g/ U+ T) Y* Y3 j/ }7 p* i2 b
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
6 h* d1 t% Y) j4 z; r8 X      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
" g: K" y( z$ o1 u- idf2 = pd.DataFrame(/ |' Q# I0 s- C' g. {- f
    d2,
" N6 f4 P$ z# Q    index=pd.MultiIndex.from_product(, x7 P7 M' ~. `' a7 N
        [days, ['morning', 'afternoon']]
% O! \: _0 h& y3 \( N4 w    )5 }3 h- Y* f, [. [  Q
)
4 Q9 V5 G$ [) ~! Odf2
8 N7 [1 w' s! i* K0 \                      price  volume. K- a$ P; w% X  V0 p8 a0 p
2000-01-01 morning       10      50
- T3 Z  j& v% e3 J0 `           afternoon     11      60
1 k: e/ i' y: _1 @& a2000-01-02 morning        9      40
( P& w# b; g4 C& F2 V- Y! b           afternoon     13     1004 `$ H" C* `( J+ R- h  T
2000-01-03 morning       14      50
9 f/ D* a0 ]+ L% @           afternoon     18     100* N/ }/ N  P& ~# l; G9 ~) E
2000-01-04 morning       17      40
4 s9 g. X% R, Z           afternoon     19      507 T. Y; n5 g" r1 Z4 \- O7 `) X
df2.resample('D', level=0).sum(); J1 E# G; n8 H0 t5 }
            price  volume
4 T) g7 v  R( }; j2000-01-01     21     110
- j: U( G! m( n+ T* l" Q2000-01-02     22     140
. X% A1 V( a2 h& x- x2000-01-03     32     150  t5 j4 i5 G7 i# y8 N. ?
2000-01-04     36      90
1 X/ p! L2 ?7 B9 M
" m6 R2 z5 M6 h1 m1 T( O& `1# @' K9 e3 W* a0 h( G
2
$ W: H* e( x2 V3
; `' S0 z" S5 A" a* k7 }% B48 I# e4 K; h4 z
5) t1 Y* u7 v( l) P2 {8 z
6- L$ {! a: w, X1 t
7$ {  ?9 _& r- L8 O0 C2 Y6 X
85 |! P( z6 w' B! `/ l) o
92 l4 H/ W4 b) o2 `. E$ L
10. Z* m* J/ K  S  D
11. n, u# W8 ?3 s: a8 N" K, @
12* p( P! Y1 ~1 q0 \& s5 Z# V
13
2 I2 q4 J, V6 r) ]9 m* ]14
) [0 @! A& r6 p. ^7 g15
% H3 c1 g, q6 Q16# F1 O6 a( ^- h! V0 _( X9 p
170 G' a! M. V! I$ v, o: }
18
$ K& [- K8 V- Q' V19
/ u) h; F, x2 ^0 j3 m3 p% G$ Q0 }200 u) Z$ K% @2 @3 p/ x, \
21
$ E3 P0 e6 _' D7 F22
# j, m% b/ J: L% D. C7 ~23
% D# s6 d  l5 I/ o2 s24
& G7 {! X4 _) B/ n" j  \. K: h252 K: l: k; {# D/ n, v
根据固定时间戳调整 bin 的开始:
' M* _" d! A( G9 e' nstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'* U8 Y8 v0 d4 N  i
rng = pd.date_range(start, end, freq='7min')  {+ |/ t; N5 _, U! d0 ^
ts = pd.Series(np.arange(len(rng)) * 3, index=rng). a9 n& W" Q- p3 j) ~! i
ts
" o- s; T  h( L1 [6 B" Q2000-10-01 23:30:00     0
/ e; m& W" A* ?& m. E: b7 a6 x9 e! |0 B: u2000-10-01 23:37:00     3* ]7 J2 m+ X  K2 L0 O6 K, w
2000-10-01 23:44:00     6
% x& \" \; p$ Y2000-10-01 23:51:00     9/ a, R  m( |( o5 q1 \3 P: q1 a
2000-10-01 23:58:00    12
$ z9 }- }1 Y+ ]0 h! l2000-10-02 00:05:00    159 s0 a9 k: M" A. p  P8 o
2000-10-02 00:12:00    18
! x$ q6 [2 e2 _! E5 W) [2000-10-02 00:19:00    21, k; M* A) c% ?9 ?! @; b9 Q' o  l
2000-10-02 00:26:00    24% H/ U" d0 ^& \4 G" n4 u5 W) \
Freq: 7T, dtype: int64
  S; B3 X  w+ q, ~+ J2 }" W+ P. S9 R* `
ts.resample('17min').sum()
2 ?4 u6 ?% B9 G# g5 z, i2000-10-01 23:14:00     0
% ]2 e8 Z9 Q( [" \! n# _2000-10-01 23:31:00     9
# {5 s$ E3 h( R- `. B5 R/ R2000-10-01 23:48:00    21
9 ^/ `" y" Z* G1 v4 z6 o8 U2000-10-02 00:05:00    54
0 n3 s! P4 N7 B) `$ Q. Q( b2000-10-02 00:22:00    24
$ k8 _5 B( Z$ M  A0 h( g0 [8 hFreq: 17T, dtype: int64
7 T  I) {( i7 l
  P/ u0 [" y$ Q& |4 \ts.resample('17min', origin='epoch').sum()
+ Z3 B2 O& ?! n1 o. @2000-10-01 23:18:00     0' m( Q. \/ x  K5 M  q
2000-10-01 23:35:00    18
3 [# n% |2 A8 Y$ B: T; C) s' e+ j2000-10-01 23:52:00    27
. c1 S$ F2 K5 @. g4 p) A8 A2000-10-02 00:09:00    39
7 D; I& E; J5 T: S4 v) C1 L2000-10-02 00:26:00    24
7 ]# Y2 P" ]1 n: ]: zFreq: 17T, dtype: int64
" R7 z+ F+ }+ \! v  z/ T4 g! g" F+ Y1 |, d. y
ts.resample('17min', origin='2000-01-01').sum()
: O; ?& y: l( |. V1 K0 I( j2000-10-01 23:24:00     3
% v3 @1 l1 w3 `/ t& E* t2000-10-01 23:41:00    156 g, I' t2 A: A- {! h# X
2000-10-01 23:58:00    45$ j9 ?& ?4 b" R; T  B& C
2000-10-02 00:15:00    45/ x& J3 y* G$ R1 B' L
Freq: 17T, dtype: int643 D- ]6 k4 H2 B6 f; X

9 N7 Y4 N( A0 S  g5 k# e& r8 X1
9 E3 X0 u' ]2 t+ V2
% r; n! ^! }- w' M3, L" g/ [. ~' E! X% f7 ^$ F
4
8 u# X7 _* B. ^% E- K" w0 c5
0 D8 S9 s: w+ X! E; ~1 t2 I6. d. V7 [" r/ F. O3 h
7" ]& X0 ^2 _, c0 Q2 _
8
0 W' e: V0 t( m) D: ?$ z. G! P9  S8 {/ }  K/ ^1 x8 R( n9 c
10
. x/ @9 w# D% f11% ?  `; h+ V5 ?( {% x$ V, V9 j
12
) u0 {) E- y$ K# r7 d& ^) C13
- T0 D  O. d, ?! H' v4 l14
9 ]5 }0 f& ^- {: Q0 x  w15
. F% W* a5 X2 x( I+ Q* E" `16
$ }1 D/ \8 P7 b% v8 V5 h17. B6 d% J% h: }  Y) Q. y; Y, X/ c0 h
18
% s9 m7 ^* ?0 X0 c+ O, Q+ E' e. v19
0 @2 J+ J( ^7 y9 H$ Z! k20
# k$ _7 r, `9 F21
$ S, ?8 ^& H! |# H* S* [, A( R22
8 {$ G5 Y+ r' b  a7 _1 M23+ `1 L5 r) T8 t# d: {' n/ Z
24
( l! j; j$ u& Y) L4 y; R25
5 E8 L! V7 h$ p  h/ M26
$ H# v- q# N' i5 h+ p27, ?( ]: z$ |. _5 d/ }
28
& {& o, k/ z1 a29
3 u/ C( H: S: I/ y$ v304 v8 \3 M" D  C" j! Y* o+ ]
31, ?( M+ r. D0 |2 j( ^" q6 T
32
1 f: M  t! r( ^( n' }! P6 X33
- p! z6 [/ q3 [) G) M: m34
8 Y  ~+ _' i, E9 k) U# e. w2 i35& p- h+ @0 K5 u4 W2 \1 f
36: [8 l& Y0 h' ]
376 ]. t1 Z0 d7 u; Y; z8 s+ H+ W
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:* b$ e& q9 A$ A+ P: j
ts.resample('17min', origin='start').sum()
, G) {2 o  g( H# u  ?" \/ d0 Fts.resample('17min', offset='23h30min').sum()# o4 \& m& {1 p& f$ G, E7 a2 O8 K5 O
2000-10-01 23:30:00     9
9 J$ x& _% l, `9 V' K! b7 g2000-10-01 23:47:00    21
$ G; M; G  C+ x5 J* F2 y3 S, ]+ T! U2000-10-02 00:04:00    54
+ J0 a) c/ ~+ c1 S, K* b+ p2000-10-02 00:21:00    247 q: F) f' p# w- H# n
Freq: 17T, dtype: int64% M- O$ \! y! E: V2 L3 {  q$ g
1+ X! A, [  c8 O$ v! G% M
2
7 w8 \% b6 o1 J5 B* f1 Q# z1 P) K3/ B; g6 C% k3 O) Y
4! f, _8 m* X6 K  e2 J9 T5 ]
5
6 i7 V& Q( ?. H$ W. b5 r6% t7 l. A3 D  L: U; M
7
+ I) R/ M' R3 y, a& X1 }10.6 练习
7 Y' I# B; H5 nEx1:太阳辐射数据集
6 `  x8 t/ A0 C0 ]& o! H9 m现有一份关于太阳辐射的数据集:3 O1 l0 V) n3 N0 x7 A
3 k# N! [9 T) }8 P0 [: ^/ y. K+ w
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
8 W3 {: e( a9 ]5 q. _! {2 Fdf.head(3)( e2 I& o  ^  K# ~7 C0 L# O& r
2 N! o3 W! ?+ H, ~5 @* v; J. P- G
Out[129]:
5 I$ v1 [$ e% D* a                    Data      Time  Radiation  Temperature2 D8 n# n0 @: y4 H/ U
0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
  v9 O. I+ e0 A, _( D0 h3 W* ?8 C5 e1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
5 C7 x$ a2 p/ S/ M  J: C" v! }, l2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
7 z& L5 A9 F+ L! W* ~& Q1
' D5 Z, U# b2 u. I2
: M" B4 c0 a# ?) ~. M32 A' z' N8 h* q% X* u% t& H$ |
4
& O" E0 @$ f" ~* `3 @( q5) `; u2 q& L" a  I* [
6
# ]5 V2 K( v' B" G7
% y4 m3 M9 A) L+ Y0 q8
! `* e9 E5 R1 j/ y. B/ m2 Q3 G将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
* U8 H. P2 F+ P" c7 Y/ @" C3 I. m每条记录时间的间隔显然并不一致,请解决如下问题:
8 e; ~7 e" x" Y7 Q. t# d找出间隔时间的前三个最大值所对应的三组时间戳。
2 |; D" U, ?7 m) n是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
/ j4 M, q/ m+ r) k求如下指标对应的Series:" p4 Z8 z% O/ W
温度与辐射量的6小时滑动相关系数
2 p" y5 s' i5 C# |5 Q9 m# \以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
* w; i1 l. O8 u$ D每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)+ A( }3 z$ x% x; T* J& r
import numpy as np6 U* A- ?, v8 n
import pandas as pd
  m% F' m* F0 A. h& P/ L) l7 k1
3 Q; d, L: v; {6 \/ ]( X2; S+ w( H7 B: H5 _/ k, x
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。5 L+ e/ w6 B3 h* `) y( a
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列6 q  |  ?) `- K# N# U3 d8 E
times=pd.to_timedelta(df.Time)0 b+ _. o" W+ Y; V, T
df.Data=data+times$ c3 B( |6 k( |& C3 |
del df['Time']0 ?& M' q6 j, i; ^: E, ^5 ^0 J. Y
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留( q& @7 s# w7 J4 @& U: D
df8 ]5 ~0 h# S' S
                                        Radiation        Temperature+ K0 z; F, z0 T5 n
Data                9 Y) t( x2 p8 X6 h9 @) X
2016-09-01 00:00:08                2.58                51( V8 u4 [# |+ T$ m! K: W
2016-09-01 00:05:10                2.83                51
! E1 H; ^. b: U- i2016-09-01 00:20:06                2.16                51
; E% e4 ?* f( ~$ q/ g2 d2016-09-01 00:25:05                2.21                51
8 y; D. t7 e) S1 d* L9 f% @# j- F2016-09-01 00:30:09                2.25                51
* t; Z2 s5 x$ S# F- u: S...        ...        ...: E; z* }6 W" ^% _/ w
2016-12-31 23:35:02                1.22                41$ G' j3 x) \  `  A, S) q" F
2016-12-31 23:40:01                1.21                41# l+ o/ e' D( t8 h/ {
2016-12-31 23:45:04                1.21                42; b( h% O) U& _. ?/ ^  }( l
2016-12-31 23:50:03                1.19                41
! P0 L5 N3 V3 O) N/ Q& q1 j2016-12-31 23:55:01                1.21                41* p' l% P( ?' @0 q; |
& L1 K. J$ w4 n4 i( O
1
+ P  g# t5 P( c+ y2' w; U/ q+ {, j
3) ^  [6 ?% m, W
4) I4 l& X" |& b
5
! X7 w8 N) }0 F. r1 c& \) ~+ ^6
! b' s5 ^6 p3 u2 b2 J+ \7
$ L$ G+ j5 z2 g! k- r( D, z8
1 D* t+ X) M* [) O. K, _9
5 p' p5 ~' M+ s3 P) |10- z% F, J1 G% n0 w; K2 M! F
11
: q! u2 P2 X! h/ [: c- q12
( Y' f" ~4 x% ?1 A13# ]6 f+ E0 j( d' Z' V: x1 f
14
! U: k6 P- E) I2 n5 g* r15
6 V& N+ P4 R3 S2 w, z9 }# \3 A16+ j7 ]6 c3 z6 p0 M# d- c$ R
17
5 o; N: V4 `# P+ ?18
3 U5 \5 C. W# }4 x/ y% l6 P" W19
6 [) s% K6 Z( D/ X每条记录时间的间隔显然并不一致,请解决如下问题:
. D$ C2 p$ t. J, R/ d找出间隔时间的前三个最大值所对应的三组时间戳。$ e$ V' _9 ]8 f3 r& U
# 第一次做错了,不是找三组时间戳0 r& D- N8 m  P: ~3 L$ I
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]5 O/ s3 w8 a' m; ^* u1 `
df.reset_index().Data[idxmax3,idxmax3-1]. I7 z0 M3 J1 W4 ~
. L+ x: k+ `4 S" q
25923   2016-12-08 11:10:42
, ?2 q& I- T6 F  V; X24522   2016-12-01 00:00:02
) h) H" Y2 b6 t& `7417    2016-10-01 00:00:19
" |7 Y6 \& E! t9 oName: Data, dtype: datetime64[ns]
6 ~. i" h1 }" e* L! M6 B7 t1) O/ o% i: l; l0 t. j9 O1 F
2% x) V! W" _# Y: k) u: |% d
3
) r) |) V7 e! k* c4
8 ~) s& E8 f8 @; j. r59 h: ^6 i! i* _1 D: C7 T
69 ^. ~7 B3 `' w+ R: Z1 Q
72 d( C# s% W# q3 W& Y
8
# [7 x6 r, G8 g! c, Uidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
' ]+ q$ I9 g) Wlist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1])): {  z5 }# O0 ^; t" C. t
9 F1 F# f! D; ]9 _  z, ]
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
% n& }6 V8 A  B: p; C* P (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
- N+ V+ g! G& t( T (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]' h7 X1 b$ S' S- q& B5 L- K1 k
1; D, H8 A/ o* Q) ~; y- q3 {4 ^2 \
2
, ]6 k8 X' d0 \5 e3
, t2 F+ r* W* H8 P  S! v) J! \+ p4
: E6 f3 ?2 d+ k: v* J) C5
7 t( ^4 N( O( P3 D7 Z' Y5 \) G69 R( K% `* l9 t7 u
参考答案:
6 b1 j! C% \' \
2 H% n  G4 ?! X! O  N* is = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()) j+ U) {# J  _+ B5 l, }
max_3 = s.nlargest(3).index
2 v! V2 ^1 E( M% m9 i5 bdf.index[max_3.union(max_3-1)]9 S( \( p1 |/ _4 K) V/ g+ b
+ Q" m' y* X0 W8 `  _2 Z: [  `1 \
Out[215]:
) z$ \9 b! d7 Q( CDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
1 q' C3 m. |6 o               '2016-11-29 19:05:02', '2016-12-01 00:00:02',% @' {  C9 J/ Z. k  i9 J( A1 a
               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],4 D3 y5 H# K3 L/ a# B: A) W2 _
              dtype='datetime64[ns]', name='Datetime', freq=None)& L5 t% |# E! H6 F- p( P( {
1
! z0 c- a7 `0 P2
; @% R/ C: R9 Q2 D3
; B$ w2 Y/ Y1 H4 x) [# u4' l/ ^! s& \5 o: i1 k: V; A# j5 z4 T9 z
5
! p( w) {$ p' J/ D, J6" F, k. ^. N! r
77 Q3 r( C2 q$ Q4 c# T" F1 S$ w/ L
8" d2 X* B  ~( |! }: [
96 p; \3 a, U: m- t7 N+ S5 c, j( J  s7 B
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。. L2 |$ Z1 ~- y$ T* }
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间' E. ]" O7 v: u* o: P2 [; b
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
) x* w  N7 h( `. \# R" es.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)/ K% R5 O  n2 {4 U1 X8 u& |; S

0 X; L/ H3 \7 N) ^(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)+ g" Y/ C1 k/ T/ {$ C8 X/ c( ~
1
& R8 Y/ E+ X" b8 k29 g% A% C( |9 ^5 V7 j: p
37 l! ?1 n- C5 Z8 z% a" u9 \! T# I. d
4
, E$ y, A3 G3 g) }1 |55 F5 ~# \1 l' V" m
%pylab inline2 q( |4 w: I* L6 v0 L4 E, l! ?
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)6 i; S% b9 h% F* d1 D7 m3 L
plt.xlabel(' Timedelta'): L& H" I& H: B8 b/ u
plt.title(" Timedelta of solar")
8 A' x, U' m6 M1 s% `0 u4 Q3 b1
0 _+ n/ ~; x& ~: M21 i) ^! T1 x* c; ], I! t
3
* [. l9 Y- _3 }$ J41 E" \; d  ]) r

/ W* m9 d( Z3 A* H" O
& }  ~; n3 ^+ g7 A0 Z/ L, A+ o! A求如下指标对应的Series:+ k. Y: W6 H2 M
温度与辐射量的6小时滑动相关系数" o" ]& w, V5 _. V3 _8 e* |
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
$ V7 F0 U8 N, M$ _3 P每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)4 K! {( v& X; K( c
df.Radiation.rolling('6H').corr(df.Temperature).tail()9 W* |) a3 [4 r, J

, y4 V" V' h1 W. \5 BData
2 d; x! q' _2 n! _& N2016-12-31 23:35:02    0.416187
2 `) G+ ~7 T( B) S2016-12-31 23:40:01    0.416565
2 @9 ^9 w  z& V+ w- b1 P: |2016-12-31 23:45:04    0.328574
' h* t4 B' S6 K) _6 S1 f/ v4 Y2016-12-31 23:50:03    0.261883) w! {$ a$ l$ `4 a7 O# F* G- O
2016-12-31 23:55:01    0.262406
) Q; W1 a" D" r  \. @& {5 v# L: a0 \dtype: float64) L2 _( M6 h/ x" R) f# U
1
% x# u0 I9 r8 _( ^23 h' V/ y* q' [6 d! p& s
3: Q3 M( \+ L9 ^
4- F# [9 r5 t; a$ C: R5 H/ T
5
. g; i; c( o& D, @6
( B0 Q, t$ B9 z" ^: R+ J77 C7 D4 u# n, `; U
8
- e' d. J( a/ G! Q0 j, ^% Z2 [97 }, V1 I/ Z  K& L) N) j
df['Temperature'].resample('6H',offset='3H').mean().head()
/ Y% v$ E" Q1 x, d* c9 n9 q: C/ N1 X* ~1 _8 o
Data
* z+ J8 u0 C, P$ d3 r' @2016-08-31 21:00:00    51.218750
, H- Q( F& `( ?2016-09-01 03:00:00    50.033333; Z& q2 V  J/ M- n  A
2016-09-01 09:00:00    59.379310
9 h  B* q# }  g9 h/ G' R2016-09-01 15:00:00    57.984375" \* I# G* l9 y4 w3 y7 U! t
2016-09-01 21:00:00    51.393939
5 a6 p( [; k& v6 W, v& }  Z6 l' C  zFreq: 6H, Name: Temperature, dtype: float64' O4 @0 k0 C+ n; A3 t
1
0 H2 s# y, D0 o# b% d6 m9 R20 B) G+ Q0 Z5 f* b5 h( [9 h# i
3
$ s- s& H. ?* E" t: P4, b0 i- |5 m# Q* s/ P
5
! b0 c' n8 m4 m3 |; ~) |# R6* ]# C1 N8 U# D% ~9 N4 ]
7
% \  W; B6 o7 I5 a3 Q3 P7 ]85 w0 t, p7 T8 R3 c5 k/ `
92 M6 R. X4 n  `; r8 G, \0 i
最后一题参考答案:( _5 T: d$ S3 n% Y
/ ^9 J  b3 U+ ?
# 非常慢' V# e) y9 [- l- G( R3 V2 o
my_dt = df.index.shift(freq='-6H')" e2 i1 O' R2 l( G+ ]2 `( o
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]5 p' r  @+ c! s9 Y
int_loc = np.array(int_loc).reshape(-1)0 y" {# Z. [6 J* X( p7 j8 o: W
res = df.Radiation.iloc[int_loc]
2 o/ l( L  i* K4 \res.index = df.index/ B. v2 u' o6 [: z
res.tail(3): Z5 Q& W3 T# B9 p, T3 Z5 e
1
; h( ^7 x) z5 W3 d# I% M8 f9 T27 j9 i5 g5 T) N
3
8 H* @7 e# ~" \$ O  Z4
/ P" `3 G8 b8 z' m8 s5
- O( G3 z% m, z& j. D6
9 k/ _2 s# S8 {7
& A% e" k8 O. m, v# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
; L- P% G. i8 P5 z) P# p* btarget = pd.DataFrame(
2 W" U1 L6 q! a    {
2 A4 t1 ~/ o5 y7 B# V        "Time": df.index.shift(freq='-6H'),
) G8 L$ f8 i! H1 O        "Datetime": df.index,+ H6 U! \) h6 f4 }1 h$ }3 g
    }
$ D/ D& U' d$ Y; e)
) t: ^2 @4 k, O3 S1 I- \; S. a; A' w/ V- Q
res = pd.merge_asof(
, @/ p+ A( T; ]/ y- _5 Y0 ~9 r    target,0 e$ \$ R( z& A7 z% t9 }
    df.reset_index().rename(columns={"Datetime": "Time"}),
. l0 a% n8 l. k( ]    left_on="Time",
" |% t* m& ]! f$ e" [; V) r    right_on="Time",) @  u- g. |5 z/ |7 a( o9 J+ Y
    direction="nearest"
2 U! A  Y+ k  R# C' Q8 ]).set_index("Datetime").Radiation* Z$ D8 C4 u( m; N  T, f8 F
' }* g0 i$ o: C$ S# L; V; ]0 n. F+ H
res.tail(3)$ J6 q% B% z, d
Out[224]:
3 t3 V. K9 G! b1 T% b5 rDatetime2 X% G  |, R, [" C% t* F
2016-12-31 23:45:04    9.33
- |0 o" y0 P" G- s9 t6 {8 d2016-12-31 23:50:03    8.49- Z& ~* a8 x) G
2016-12-31 23:55:01    5.84
8 E; e; L6 F- N; S) b% C3 o. k! IName: Radiation, dtype: float64
+ n6 C' g4 K% r
" o. o5 D% W+ S! F% Q( J9 e1
: a% \8 ]+ P0 V24 [! E' ]' c/ x5 Z+ T4 ^
3; W, J8 T( }( y: Q
4
. x2 \0 h! P' B58 B4 r" ^4 R. }) A$ v, y4 @
67 j& F0 _) s5 x
78 ~$ x* o) l# g9 n% R+ T
8" \6 D9 ?9 Z; c7 [
9# i, h4 |9 W2 ?3 Y
10/ m, C% B" O6 l& `+ T! Z
11
8 F' {7 @1 H% i; u, p12
1 x- U* J6 F. P1 I* m- |13
0 F, |1 k+ o" N: [14
. g- l+ m/ V  r3 a9 Z. _15( q! `- ~+ B4 ^7 e: R
16
' E$ A# [- ~& w) [' z# s$ M17
, W( L5 k8 U! q6 s  f183 {4 F! j; i, O6 h1 D% a* d' s
193 D* T6 r0 r" {- U$ S
200 u, K' r; J4 }0 E
21
& M2 `8 I8 f, d! m# {! ?7 n* x22
& ?4 x# [; m5 x- g) [) {23
5 T; h6 j) G" G+ S0 d3 ]! y5 i4 aEx2:水果销量数据集0 ?# {; f8 H: a; {' e, `  Q3 o) U& l- W* ]
现有一份2019年每日水果销量记录表:+ A6 B7 t- K9 o7 w/ P8 s

2 k( Q! B, o( Z$ ?" M( ndf = pd.read_csv('../data/fruit.csv')
8 j# f; s, s9 Kdf.head(3)" H! O8 ]: R, t
8 m) x  i6 p' L9 o0 K# _- X
Out[131]:
6 W) r. @4 R. n         Date  Fruit  Sale$ R* ]& z; m. y( ~  k0 J( d
0  2019-04-18  Peach    15
1 y( r$ K& L. _1 ~" r2 [0 j1  2019-12-29  Peach    15
" C) {& Y, @4 g1 e2  2019-06-05  Peach    19
1 a7 p& Q2 @1 Q/ O* _. P# p1
: ~8 a: t  z' ^! y1 {% |2* L( z' d) ^( ]* }' ~5 Z
3
6 I1 q2 d+ }7 r% s! ^, P2 q( m4
* R: x5 ]( q! c6 e54 ~0 B/ `$ c- G7 }
6" B6 b# v2 c( J* p+ @! c2 v- d
7
( C( E+ ^" w5 v$ `" d8
$ M  D* ]  I+ q* f) o+ {统计如下指标:
+ O& R4 B1 u9 d& l5 N, M/ ~& Y' D每月上半月(15号及之前)与下半月葡萄销量的比值
6 u! ]! F# q5 o: W; K2 k每月最后一天的生梨销量总和+ w8 M4 Y$ ^1 B) s  _! X8 f
每月最后一天工作日的生梨销量总和
5 W; a" B) z/ f每月最后五天的苹果销量均值
4 d/ U- F5 ?  J; C8 O按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。% b; J+ B/ A5 C5 y- _+ _4 n
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
$ z# O; X5 W+ V3 q( h+ Uimport numpy as np
+ c9 I+ O' x1 ^import pandas as pd/ _1 Q: @  g( k, N
1" j/ P! L3 U, n5 k! c
2. K0 f: ~6 }. |
统计如下指标:
5 d' V8 o: L! V* a, W每月上半月(15号及之前)与下半月葡萄销量的比值3 ?  f" ?6 [4 J, R
每月最后一天的生梨销量总和
. d- z5 o# C  s6 m每月最后一天工作日的生梨销量总和) q5 g& }/ f1 }7 j+ G
每月最后五天的苹果销量均值( ]8 a/ X$ x% O- g
# 每月上半月(15号及之前)与下半月葡萄销量的比值
, i1 K1 s' N" T5 cdf.Date=pd.to_datetime(df.Date)
9 d. k1 {, i$ ]# e- d" \sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()6 @% R  k  [! O& h. Y& D, [( C+ w: }
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
. d( g; }) X4 d8 X- w: t9 zsale=pd.DataFrame(sale)
5 C# [* `; k( g6 G/ ^7 _% h+ asale=sale.unstack(1).rename_axis(index={'Date':'Month'},& R( s+ u2 o7 _
                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
) o* @# g, n- t1 hsale.head() # 每个月上下半月的销量
. g; v2 {" i  `2 y& A6 E: c: Z$ m$ |* w3 V9 Y9 u. u2 ~
  Month        15Days        Sale+ f# \; v  o% f+ u
0        1        False        10503
9 m4 l) b3 `! @) [! W1        1        True        12341
  }* M! d( i" j, k2        2        False        10001
4 j5 X8 P- i( a8 r" C; R3        2        True        10106
1 E' I# D' A- U4 o2 C; f& c$ D' k4        3        False        12814, N! Q0 W, K3 L1 N' v1 o

' a4 W' @) H) ?# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
% @  L: V/ f# ?. a; ~sale.groupby(sale['Month'])['Sale'].agg(/ \" Y) T' c8 {5 ^7 F/ R# y
                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())( P% A4 Y+ K$ I3 f; F

8 F3 ^! ]8 z: M1 g9 dMonth
& ~* m1 A# R/ A  y- r- _1     1.174998
- k9 U9 o6 j4 I2     1.010499
$ J- p# W, U3 j; N; r3     0.776338/ h. ~- `, k5 `0 L$ a5 K
4     1.026345
% j; M: f7 y4 @0 Z9 ~/ T. c: J5     0.900534, R$ `) N9 ?& [/ p: R
6     0.980136
. r0 h; g3 k  e1 {6 K! o) a7     1.3509602 r8 _2 O- q: n3 U/ q8 B4 E) o
8     1.091584) b% I* V- ]2 _) D, u* J  \
9     1.1165088 ^" i2 {8 T* \
10    1.020784( p% t- Y8 W/ Z* n  E; U; p
11    1.275911- a, p( b1 s; c+ F
12    0.989662
$ [2 R" m3 O, v$ M3 q# a; [2 |Name: Sale, dtype: float64
" t& G- t- v6 e  l( O8 T
. Z1 d0 m/ F9 t" t5 m1
- g! H2 ?6 U- k; x& a5 W: k2. t. o6 C8 V: {
3& Y% I( ^8 e7 s# C- h
42 @6 I- G' u: b  G1 p
5
0 u* `8 V1 `1 h/ a" S6
8 F* K! s: J4 ?1 ^  }2 Y9 U# \7
) o/ D9 ~. O" W7 {9 f5 z8 b* w8) v, ~2 j( i$ E  h5 E3 g
93 F2 j; U% E9 J0 P' V3 H8 ^
105 U- E* K' B. o! \. t
11
) k$ y' _4 [' D: w# r3 ]. @' n12" o1 C9 f( J2 j+ D( o7 m/ f, t3 C
13' `7 k3 y* ]$ T8 c3 v
14+ j! v. c4 {1 J
15
1 E& |9 o. w- r6 Z9 ^/ v16
3 ?  Y! M1 `0 O5 ]17
6 b$ S5 i" ?. @# a3 W9 }6 [18
* z. v+ I: _/ n5 r6 S4 ~19
4 x  K" z0 @$ l1 @# \/ q% Y207 j9 k. N- [1 N6 o
21; x) H. m& Z2 ]4 {8 T
22
" i- W6 c' o7 D6 w. q+ ^1 M8 {235 S9 T& f/ }( U' @& x
24
# ~* G* }/ c% }" \8 O  n25( i" F9 Y' {+ K) r, v
26# ^# m6 b8 P; A/ T8 P: C' s- G
27
7 P. h2 d& ]! R' e3 l28. T7 z, c- [) g/ x8 G5 X9 s4 i
29
4 g: P/ t) t: h. S309 {6 M6 Z- n$ L; a0 A) g* q! C$ V3 `
31
0 `2 n6 _0 M# X32
9 \% _( w' P- b# [33
8 ?% H1 P! i! h+ n$ U34
& _" \0 x0 o9 b4 C# 每月最后一天的生梨销量总和5 f/ N% U8 N4 \( G* b4 U( h! D. a/ v
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
! y8 T. V# N# p9 H- t+ h4 j0 e: B' J: V! M" D
Date
# b2 p# [8 ~) H* G0 B) i; \! {5 O2019-01-31    847; ~! D% W$ F/ h2 A
2019-02-28    774
. ~3 f  {8 j) V- O4 c2019-03-31    761
% R) I5 Q* F$ ^& e4 U' Q% T2019-04-30    6480 l$ |# v  y+ n
2019-05-31    6168 b7 j( a) p5 Q, a. P
1, O2 [! z# k+ G, C7 J% O+ \" N3 j
2" b! i' x1 ]: v8 p4 H/ z" y; B
3- l& J" q$ i2 j  l6 m/ Z
49 }7 ]+ x2 I1 T5 }5 o
5* H" n! Q( R6 }3 C; {2 y5 P  U) p* P+ R
6' ?- F. L1 T' p5 d
7$ [& y8 _8 M/ D! E$ b
8
- f' T0 q9 {4 n1 B! @5 z. n- O9! y' J1 R. N6 L* t0 r
# 每月最后一天工作日的生梨销量总和
/ k6 }, j  B# Lls=df.Date+pd.offsets.BMonthEnd()
: j, D+ m5 ~5 ^4 N6 p& g# o8 tmy_filter=pd.to_datetime(ls.unique())) u5 `5 l& C; u$ g, h3 t5 \
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()/ s& t! z; z+ e5 I* e

$ r; v+ m+ C: j9 e/ WDate$ [% X+ ?9 O/ V6 k( O$ w3 @3 J) D
2019-01-31     847; x: ?. d) |6 ^* C  Y, a" b9 a
2019-02-28     774
# _/ e  C# y" Z* ^  P* U  x7 J4 H2019-03-29     510* i. h" [; U) {( h  f0 S; ]* w
2019-04-30     648
* t  p7 p% }5 |/ G$ x$ r" v! b' @) k2019-05-31     616
2 v$ u6 q/ A5 T. y) \, j6 N18 i4 \2 ^0 g( }9 Q0 |- {( f
21 Z) C% |7 a( [  A. G% B
3
8 h, U8 N; h" j9 B9 o3 _4) W0 m: c$ s" J4 z9 f: s
5
/ |+ S4 a3 O- _. n* l6. T* J% T$ ~2 d/ `" J& I
7
& X1 C  R9 n; W6 o8; [- C( k& o9 I) D1 g9 P+ O
9
6 b9 H$ U# b, d4 Q6 A0 _- Q) ?) D10
7 w* V! P5 W% W6 U: c. O- |113 _# B! X9 Z0 W5 r$ x2 M: d
# 每月最后五天的苹果销量均值
7 V4 T' }0 n* u$ @1 {9 i  rstart, end = '2019-01-01', '2019-12-31', r* [. T3 b6 h! n+ q' P% d8 R- v" d
end = pd.date_range(start, end, freq='M')1 X) \* i7 V; w' e4 Q5 ?( E0 {
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差4 m1 x! L$ U$ q  E! w

+ D+ _) W  ~  I9 ntd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)3 x) M% e2 ~& [5 v9 L
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天# q, u( i* H$ _
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表% F! U) a4 H( w( V; [

) g- X9 q. W+ Q& z$ h, m" i  P8 Eapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量& g! y5 C8 S& O1 H
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head(). f0 n, Q) U1 v/ j6 p

3 }7 i/ _. q, {3 l& M7 MDate
6 T# Q; X  X& H. t( f1     65.313725( P# p/ \; M) D; \: V/ c1 }
2     54.061538
1 A6 `& i7 N- I3     59.325581
. t8 g; x2 k$ |- N$ |4     65.795455; U6 k; G0 m3 E4 Y1 w3 Y) Q
5     57.465116
9 \) J9 r+ y6 a2 p, i  D5 q1 t
* a9 K- i$ Y, a/ D% s1 E: g& N1
2 _' @- ~) i7 o( O' ]8 b27 ~. D+ c1 U. u* ~2 x
3
+ c2 C+ `) X) x! f* J4 u4
+ X7 n+ B6 Y/ J5+ Y7 C: J% A- n1 n# |& y
6
' S$ D; y, P0 a  T% j1 @* l7! D5 E; g3 o& S* U
8
8 i) F2 J2 M( s( u) j0 U9
: ?% g) d9 u2 m9 _10( O! y7 V; o4 y2 h! K! e
11
$ y+ F0 x6 Q" u9 i5 n' d  N12
/ M1 \' x. B9 @  v132 I2 f, V' }7 ]/ f
149 E8 @# X1 s8 y# M% m$ E; T
15% Y7 U, ?/ d# H" ^0 t3 V
166 j8 L& w5 Y- r5 [- A
17# L2 E/ V2 w' n
18
, g! ^: ~+ e0 q; ~# 参考答案:1 G' }8 R0 J/ {2 P
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(! i9 ^8 I5 N1 w4 y% g; f1 ^, \
            ).dt.month)['Date'].nlargest(5).reset_index(drop=True). m. S/ l3 I% |- X6 u
7 p6 M# }$ u" M* j8 m4 ]  Q
res = df.set_index('Date').loc[target_dt].reset_index(
' f9 v7 H/ C) C4 y8 I8 h            ).query("Fruit == 'Apple'")& i9 {# t: z, Y* s+ L0 c
" p2 d6 V0 |4 p1 U7 r
res = res.groupby(res.Date.dt.month)['Sale'].mean(
% @, g/ f* [2 I- @            ).rename_axis('Month')
2 n$ e: r5 S' a6 k9 \  m- F- C2 ^* @7 l9 d; b! j6 X! e
9 b. x; F" k! Q# Y! E, h+ D5 ~% K
res.head()
# n7 D8 a2 J4 Q4 m9 r5 {, a* LOut[236]:
5 F: C2 w) o" b  G3 L5 o) i3 PMonth
$ v( U& U( _+ ~: n$ N. X( T1    65.313725
* l; q  Y* e1 k, _2    54.061538: A/ r( N9 g7 K3 b
3    59.325581: }5 R  Z& D5 N% ~* U3 q
4    65.795455
. L/ Q6 ]! H8 w3 I" s6 E$ C5    57.465116
$ m8 @& Z% o: e9 i8 I0 {. Q( IName: Sale, dtype: float648 J( W) s1 }8 d/ e
* o' ~7 ~, i# X. H8 h. A9 N
10 O5 Q" b3 N) P" e8 l; M2 n$ K
2
/ Z/ q8 t$ j: [2 c6 t- A% R8 @3& e* I! G0 ~: r, E1 W* d
4& W* ]. a" u, v" V2 V6 a$ w
51 z2 N1 B! G6 ~# h) q. o5 m
6, I$ {0 L8 i1 U' [% G+ {1 ~( r' L9 [
7
$ A' t+ V, @% I! l% ^8, C# N" {7 w5 N  G* V; q
95 M* n- h. _$ v" t- g8 `
10
* X5 T& y. f  r% C7 P/ S7 G115 M0 j3 v' k1 n3 v6 |% ?
12
$ _! ?3 Y( _6 F/ Y9 W. ~13
; A* o/ t; M: L" B) D; }14& D, m  g# W9 J3 N
15/ i3 W! e( s0 Q! |' p6 D
16
' Z* e; J0 N4 }; T( ?+ U17
" G0 a# c7 `* c! ]% ?* q$ j18
' Q- K! q) q+ V/ v- l! |# u19
% ~% ~! S8 R6 W" K% D20
: ?6 L0 P, E! G按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。+ y, I( Q0 S5 j* `3 S
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
: \. T& ?. k. [+ l9 u# Y                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
2 p$ N2 _+ n1 A0 J                                        . y* X7 ]0 L5 M/ _* {
result=result.unstack(1).rename_axis(index={'Date':'Month'},
1 m* Q4 y; n0 @  I1 V/ O% u3 g                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字., n2 }" N. `. h1 X3 p# N3 y
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)/ F# C5 S9 y1 v0 o
result.head() # 索引名有空再改吧7 Y) T/ g8 ~0 v! O) r& ~8 M
: p4 {- a" ~( _" N, x/ j- _
          Week        0        1        2        3        4        5        6
" ?) b# k+ ^* CFruit Month                                                        - T6 z5 l3 {( A: C0 r2 p
Apple        1        46        50        50        45        32        42        23  B& Y/ L2 s5 b/ h0 m. {
Banana        1        27        29        24        42        36        24        35! Z5 n, i/ n' \
Grape        1        42        75        53        63        36        57        460 o  {3 ~  `$ A
Peach        1        67        78        73        88        59        49        72
2 e) B  z% a4 q8 J. B( q. rPear        1        39        69        51        54        48        36        40
7 y8 P$ ~3 U6 u) g8 V( f  l) N1
6 ~3 l/ X" E) \, ~2
" h6 s4 j; K  J' s4 O5 m3) W9 j& Z! v6 a1 m( ~/ X0 }
45 Z: r( A, D# h) C$ B+ T" k
5. N4 B9 `6 W0 H% _  u: n; `7 [# ~
6
- t/ x# U# Z; d# |5 c7' t$ c) \/ e( x- r
82 E3 l. l$ g% R9 l3 i* N
9' q+ I; Y- R4 K" I- K' U
10
* b7 ^& Y0 h2 R2 W6 p4 j11
. D; v  e6 e0 n* S: F12% ?* v0 ^' q5 Q' N) l, c3 k3 [
13* _4 t3 }( [( a! r/ s
14# \& `0 q! o, I. g+ x, i
15% _( I2 y& _$ w
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
: k9 W7 _. R6 G; w# d# 工作日苹果销量按日期排序5 l# H: K) Y+ y: G# G. H
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()" T9 J" S  b7 ~7 M- }! l( L
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总6 }+ `' G! P" u! M# I
select_bday.head()$ V8 R; [' Q3 S8 w, E

8 v5 O* ~2 {* Q  RDate& k# e" I# T2 @1 F! H
2019-01-01    1890 s. D  Q) u8 K5 |" [, f4 V5 ^
2019-01-02    482
* v* V/ P3 T: F2019-01-03    890
$ z/ W6 D  y) S0 U4 S2019-01-04    550
3 C7 I2 T2 u* Q* n& ~& T0 E5 b2019-01-07    494" Y8 g6 c* U# a# p1 w5 U9 P% ?

: c. ]6 V4 Y7 r! C# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。5 [4 r$ t  ^; m. A$ J
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()9 h% C' X' a5 v( |

( w% d, _0 i# F& ~7 T; X$ H9 N! mDate$ l) s- r# n0 Y* ^- b
2019-01-01    189.000000
" _$ m" s! m2 Z( r& v2019-01-02    335.500000
  M2 J. n# G. [9 C/ e: o" c2019-01-03    520.333333
, t+ }* c; u6 c' K+ \  L2019-01-04    527.7500008 j, O0 y. f2 U9 V' g
2019-01-05    527.750000: L% G% U  m; T3 C: w, n; J

( B9 {7 a! u7 T) y  L————————————————
+ s( |" Y1 V+ ?# B# b* Y版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。2 f4 q( ]( A3 h. D1 t
原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913: C3 K( c2 Z9 R  {6 H: N' _+ {

1 Z" Y6 n7 L) ?, c/ o2 o% U  Z! S* n4 i





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