QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2764|回复: 0
打印 上一主题 下一主题

[代码资源] datawhale8月组队学习《pandas数据处理与分析》(下)(文本、分类、时序数据)

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2022-9-5 16:11 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    : B9 h( ~$ B) R3 E  }$ e7 b/ E- l
    : l8 a0 y- b1 q. [! O( y6 T

    6 t0 u/ ?" D8 p1 i文章目录) H: r0 h. _. x3 c( Y. V2 C3 E
    第八章 文本数据) y% `3 W% ?# n* c4 L1 J
    8.1 str对象
    2 ~# T  f; q  D6 A8.1.1 str对象的设计意图
      m: r, h4 Y- C8.1.3 string类型! X" Q& V* |% p9 D/ T/ O0 p
    8.2 正则表达式基础
    ( u: b1 |$ q/ W, v+ S# f/ m+ o" H8.2.1 . 一般字符的匹配
    - v" U# g  t8 j6 @% e8.2.2 元字符基础
    4 ]- {) X9 b+ @1 ?( u+ Y; d8.2.3 简写字符集
      B0 @% s  K+ t2 d8.3 文本处理的五类操作: K4 U% n0 @+ w( x7 V, J
    8.3.1 `str.split `拆分
    7 D7 j  G6 H# R1 y+ N8.3.2 `str.join` 或 `str.cat `合并2 O* q! p0 M' R0 r1 X
    8.3.3 匹配
    ( a! o$ @0 T; j0 `  Y8 E0 [8.3.5 提取1 \$ @1 r3 h; k" y7 Y  ]: S
    8.4、常用字符串函数
    $ D" X4 ~! R2 b7 R$ ~% s& \8.4.1 字母型函数8 F0 o& D1 x1 m
    8.4.2 数值型函数, A6 X( K$ O; S) r, l
    8.4.3 统计型函数7 H1 r0 c5 F. C, ?; G
    8.4.4 格式型函数: Y" m" |) O! d6 X  p
    8.5 练习$ L! u8 ?7 N: D6 R9 x1 c
    Ex1:房屋信息数据集
    5 |# G( U) }  J8 MEx2:《权力的游戏》剧本数据集! ?" h1 _8 c. B3 F$ R( g
    第九章 分类数据
    ! t4 n$ ]1 `) }% `$ B# s. Z: w9.1 cat对象  h7 M6 C6 Y, E# W7 C+ E6 q5 w
    9.1.1 cat对象的属性/ d0 ^- N8 f" R, M5 g9 v
    9.1.2 类别的增加、删除和修改5 d1 H* f, @" E% {, |5 @; s6 a3 k8 T
    9.2 有序分类! J/ a1 ?! {0 A9 Z: Q* v
    9.2.1 序的建立
    " M6 r, W& F1 d7 t) J9.2.2 排序和比较
    : `% |( g! f% O9.3 区间类别
    6 k; U2 x5 R- c- X9 X/ ]$ o- O6 d9.3.1 利用cut和qcut进行区间构造2 l' u! {1 h- R# w/ T, ^
    9.3.2 一般区间的构造
    , b5 Q) G" o+ l: Z, F1 p9.3.3 区间的属性与方法
    3 f6 n& _# z, w+ s! T9.4 练习  h9 d) s  }" e5 l
    Ex1: 统计未出现的类别
    ( i5 a) Y% v: z7 f9 \( WEx2: 钻石数据集
    0 F- x4 v4 M0 o2 d% e* @+ a# @8 s8 D第十章 时序数据' C. Z5 w: Y4 X  h/ R- [  b
    10.1 时序中的基本对象
    0 x) z* [5 ^/ p9 H10.2 时间戳+ F7 e% o2 \' A8 L
    10.2.1 Timestamp的构造与属性' r, G5 t* b+ v' G( u
    10.2.2 Datetime序列的生成
    / C! U! p1 U$ k10.2.3 dt对象- @. v" g- \) }
    10.2.4 时间戳的切片与索引
    ! P0 O. T6 u1 j* D+ E& r$ T* F10.3 时间差/ e4 H, h# h2 d3 C3 s: k
    10.3.1 Timedelta的生成
    ( I' {6 H9 c: g' b1 W  s  E/ `10.2.2 Timedelta的运算4 ~. n3 N; A, C* M' _* H# z
    10.4 日期偏置
    # W/ p0 K! _6 W4 s10.4.1 Offset对象
    2 q/ Y5 ^2 r" x  U1 {+ o10.4.2 偏置字符串
      ^, E9 G) X7 `* |+ Z1 }10.5、时序中的滑窗与分组. o) K) K: b+ C
    10.5.1 滑动窗口! X  Q0 P' O% |- {( r
    10.5.2 重采样' L. i5 C. C9 O2 H0 Y& M1 q
    10.6 练习
    % ^/ q9 ~- e6 l3 LEx1:太阳辐射数据集
    8 W8 p* k3 \7 c# f3 cEx2:水果销量数据集
    5 U2 k; l9 X* b( l) [7 \, Y  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网* @# P) h) I7 ~) t2 o; {* \
    传送门:
    ( Q$ b2 v- s) c
    ; {7 K" A+ D7 d* A2 c0 i0 K$ jdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)6 E% x  y4 K" W2 H& ^$ R
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)8 t2 |/ P; ?* s8 @3 }' r- J8 a
    第八章 文本数据! z* W/ K4 ~% H( T6 J* d( X
    8.1 str对象
    ' C% [6 r; O3 B: @7 Q  Z! Z) Y2 I* h8.1.1 str对象的设计意图2 {8 [, H7 Y* [) g" N
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    7 N/ ^) M4 H! z! T$ Z( ^' x' E
    5 [, p, q; R5 ?- t* L( O- ^/ q8 lvar = 'abcd'
    ) W* C9 h7 s/ k- l5 {2 c# F# J1 nstr.upper(var) # Python内置str模块
    ) s" A) w* e: K4 Y# LOut[4]: 'ABCD'
    ( P0 f" `+ t' k3 ]+ q3 b, n( i$ X$ F: ^. s# w5 Q1 @- G
    s = pd.Series(['abcd', 'efg', 'hi'])9 s. x3 p- o+ G1 X3 h

    % e7 }( t6 x. H, w# Cs.str
    : z- Q) J# {& ROut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    , f* E/ M/ \) l6 i% |' _2 G: V! Q# t2 x" k
    s.str.upper() # pandas中str对象上的upper方法5 b7 q) C# {! P5 `2 L
    Out[7]:
    1 k9 _5 ?9 v7 D5 t& B+ W9 a& C! S0    ABCD
    , }  a2 ^3 i- A1     EFG. O! ?2 A5 |5 O; _- G
    2      HI3 m$ l) z$ \2 `/ y2 @
    dtype: object
    4 q0 M9 Y7 J: K$ ]0 K9 O1
    . D+ v  \# T; y2 w: v" z. [& A" n2
    & g' `$ l. t" _: H* m* m/ O. j3
    ) j# H0 c! Y% g9 a5 V! m2 j41 g* Z, I* q  H$ I+ f
    5
    7 P! n1 g3 W  a$ O6 u2 B% s% I62 y0 W& [! U" A0 e$ }; N# }; D
    7" @8 w% s! ^. y& p. A$ d, N: N, Q' w
    8% Z& e# z9 x6 E/ z" Z+ {  R6 y. n
    9/ Y. J1 k/ U7 M) B
    10
    : d/ S- w+ J9 z; e& s/ }' q11
    ; y) M0 U! }4 _% [( P+ F128 v$ H- }6 d+ w7 R& k6 d, ~
    13
    . d" w* E! x, {& e14% `! G7 L1 S. I- @% m
    15
    * e. v- k2 _' k$ q4 C8.1.2 []索引器$ O. z, v& M4 \  t
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。, C; Q/ C0 F" P: V- l2 ~) x% G) c
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:/ O" |3 B' V8 ]+ N$ q

    + I7 i/ n. w/ o" x6 {/ js.str[0]
    0 w5 Q4 w) X$ m! N/ GOut[10]:
    $ ^. l& b! u( i& m! p0    a
    ! ~8 X3 y- x4 ]1    e
    $ F6 M( x. J0 M/ i* ^2    h
    + O0 X6 I. ]& C" }dtype: object
    $ `1 [% y  V6 @( m) x* S9 H. o! U. t' v  {
    s.str[-1: 0: -2]( h4 o7 D9 m/ K7 ?# O% x8 J% l
    Out[11]: 5 J2 F$ ?: \0 l2 Y5 y; |* h6 I- O
    0    db/ i0 ~, y4 h( O8 ]
    1     g
    1 p7 b. `) o9 ~$ V/ C- }5 k2     i7 V( X, W, A  p: J
    dtype: object
    8 v- @1 }8 E+ m8 o1 `7 t. Q: Z& C0 s9 U- o
    s.str[2]
    9 u3 `& t8 m3 X( }+ B# fOut[12]: 0 A- U: m: \/ A
    0      c1 T3 j( d4 A6 H
    1      g
    4 Q7 t- f. d( }; y: W2    NaN
    1 n% F. d8 E" K: R( _dtype: object9 E0 h& ]/ p- ^6 s

    0 |9 V7 j3 m7 L) Y1
    7 |8 N. t9 \- C  g4 Y20 x  S! C! P# ]/ G& P3 n
    3" c, w5 c! ~- `5 @8 W" K
    4' X- C- ^7 d, i/ B. e0 N
    5. c5 v' }( R5 Z
    6
    . R5 O5 V6 X" R7 h& D$ V# ~; j7) {& f8 Z" V$ _9 L* `& Y
    8
    0 M8 E% _% d% r& C9
    , h: `4 W* H; l6 ]' ~( f+ P105 f& v8 l9 w0 E3 N  R6 S+ S
    11% A. l7 A& ^: \8 [" R$ l
    12
    , Q7 j" d8 ~' F3 }134 ~8 y, n- ~& l
    141 F, y* y' S% t. E
    15
    3 H, K4 W# a; H( Y& `- m16$ ~( O' B; H! d. I2 ~
    17
    3 F% }1 l. [3 [7 P18) J& F* D3 `5 {$ c+ G  a5 @
    19. ?  t0 G6 ~# g3 v# h
    20
    & z3 x" x" f+ D+ A5 Aimport numpy as np
    . L& F; a) w# {& @: p, J9 limport pandas as pd( x4 r$ I9 @4 Y3 q+ o

    ; S7 d1 f2 T+ Zs = pd.Series(['abcd', 'efg', 'hi'])6 B: |* x1 V1 m
    s.str[0]
    ) {4 G7 b! K" r# a) a# O1
    * d0 H2 G+ Z, i0 P# b$ ~+ A' V2& O+ e' F; ~  c, I+ Y" Q8 y# m, W
    34 J& Q+ w: }9 \: {
    48 @9 c2 A1 @7 R, }+ S7 L6 Z) w
    5' _5 [) O7 S, P+ A2 ~3 o' S+ Z
    0    a  p6 r2 B4 g2 T# V: D  R# |
    1    e8 Y% e: c# B" c
    2    h
    4 A" e; J" P% o2 X3 F0 h' B3 Pdtype: object
    , P3 q( t1 g; G4 n& B1
    / z( i+ S& ~+ c. Y. w0 D/ d2
    ( _% _% f* q0 y9 F; ^1 Z3, |% `( L- T: d. E3 W2 p- T; f# |
    4/ O. O9 z6 P" F) Q
    8.1.3 string类型
    : u, q8 p8 w$ ?: j  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    9 m! ?( W" N( y6 `  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    $ N- Y" |% [+ Q" q  a) U
    6 n1 M! E. Q" d: f二者对于某些对象的 str 序列化方法不同。
    ! S9 @" B, ]5 U! H可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:- `9 t4 q+ w$ E1 K/ t( a
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])6 s5 E! y& P; T' _4 {1 ~
    s; q6 R1 p' i$ |8 }4 ]* P. _" w
    1
      H" E0 J- @( f8 A+ r  X/ m2/ d+ _1 q/ d, Y  y6 c7 K
    0    {1: 'temp_1', 2: 'temp_2'}, F- T& W3 Z% A7 W
    1                        [a, b]4 v# _' u+ V5 v, ~! l- J6 L
    2                           0.5/ w* ~* j5 h+ j, k9 b/ K6 H
    3                     my_string
    2 |. @# ~( s( `# A) S3 pdtype: object, U  f; \% O# a6 W
    17 _# C) }3 E' T  d3 B' y! H7 i( a4 Y
    2% o) Q5 C% l8 [  r' H5 u. ^8 h& c
    39 e1 [# r( L9 \9 f1 F- y
    4
      |. w+ f1 R" ?! A5
    " @$ V9 `1 Z* Z2 E3 @6 g) p9 F2 z: rs.str[1] # 对每个元素取[1]的操作  Y( C% X0 P- e3 K* q
    1: M5 _) t$ Z( B) X" C: r
    0    temp_1/ J( V6 i, Q0 l9 Q) y' b
    1         b
    " `. ?7 X8 Y" Z$ R4 E! K/ E2       NaN6 C9 o" P) ]: u
    3         y* F& \5 d7 d, @2 ~
    dtype: object
    " Q' B$ J0 z2 q11 B6 R' t) S5 r% \1 v
    2+ V# E; ]- o( Z3 _6 }9 A. {
    3- N4 H0 X3 ?4 c/ M4 v9 w. K( p
    46 x$ l$ `+ t% t' z( B5 C6 y/ D
    53 p/ ?  x) V, Z  `; j8 Y
    s.astype('string').str[1]
    " L) {* @1 g- P: W; ?$ Y6 j1
    9 h1 N& J! D; B: `7 x0    1
    " `2 B! F0 ?  p2 I+ Y1    '% i" q. S- S. u* n5 g- Q3 s
    2    .
    4 d) K- J, E! D+ S0 `0 I, E3    y, V9 k# V4 g6 L( \! V- ~; y4 G
    dtype: string
    6 ?- _* |: s! w5 ^& v15 E- D! A, m$ r) e( `- x. p! S$ `, \
    2
    ; `5 M) f% i" ?! u" M7 O8 b3
    ' C5 C  t8 ]) H) `& f4
    1 }4 Z: X0 w4 ?; o% W/ A$ Y; M56 y0 H( h# }9 u
    除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    ! _, V; z& W# o9 D6 f# r; I' B) t1 t4 b8 K
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    6 {1 @& D& x- ]; B4 `' n  B0 {string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
    " [" _' g, ^1 b. V% Z" d; bstring 类型是 Nullable 类型,但 object 不是" M" N* _' E# F  b6 U
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    % G0 d' S# [4 M8 |7 Z6 Y6 o+ B& B  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。2 `* n5 P7 Q" J' v% H7 u3 a2 x
    s = pd.Series(['a'])! B$ ~$ V. [! W+ _' b

    6 h' `) g; n4 i( _2 @s.str.len()
    / G: O- X2 C1 j7 fOut[17]:
    2 m( L! a! t  W6 V2 R3 W3 t  P" p0    1
    ( Y/ B, T9 q: _8 i, Mdtype: int648 ^5 E7 }2 Y3 K$ e' |9 x, L$ j
    6 {* Z0 E0 a: N8 t  ?
    s.astype('string').str.len()3 O" w4 B$ l% Y( W% d7 Y1 |
    Out[18]: & L* q0 ?& P5 y
    0    1* n+ V5 P5 D* U: N' O( b0 k' C
    dtype: Int64
    3 r8 v9 S1 E& x+ \, e2 k8 d  H3 O" J5 e
    s == 'a'; p! v( s0 E  U! J- G* k6 I
    Out[19]:
    % u& E' }! E* Y' X. s% q$ S0    True
    ! q+ `# V) v, i5 mdtype: bool
    7 B% g7 P. ]" M) S8 }' J7 Z4 d
    ( |( ~( |2 J' E3 V% i& A8 ss.astype('string') == 'a'/ v9 c" h- N4 N) I+ Y7 u
    Out[20]: . P# K+ d5 K$ [1 L  K& k
    0    True
    . `: c: M7 g) c2 F0 jdtype: boolean
    4 e, ~8 K5 H1 f3 B" j, @7 K! r' d  `& ]4 i1 p2 P5 u' i
    s = pd.Series(['a', np.nan]) # 带有缺失值3 ^% E0 L5 t+ Q% i. c3 h5 G
    ) D! ^, S7 a6 t8 V8 |
    s.str.len()+ L7 L! Q5 w3 c
    Out[22]:
    . X# b! S& N6 L4 |0 D/ E, @0    1.01 `/ r6 V- O+ Z, L2 ]
    1    NaN- g8 i; G" V/ Q7 H; f0 B$ d
    dtype: float644 h3 Y2 B2 d+ {* A4 o0 _/ n7 ?
    4 n- }& N. k! ^
    s.astype('string').str.len()
    - g) ^3 n1 D: r$ q4 _6 j5 Z* G5 YOut[23]: 8 f; M7 h) F& Y6 X" }3 O
    0       1; h9 w2 _% M) J! L
    1    <NA>
    ( ~& @0 L' V2 |dtype: Int64& m5 q; A6 n7 G

    / ]0 I4 \/ N3 G& v6 as == 'a'
    4 D( Q! y, h0 M; qOut[24]: 5 r9 w! r5 e2 u) G. v" V
    0     True! _6 w6 x* X# s2 s
    1    False: z7 L' m6 L6 k4 `
    dtype: bool
    8 Z/ I4 _. P! q8 x
    3 h7 k" @$ `( m; @, \s.astype('string') == 'a'
    / `# h  G8 I% V  A. R7 mOut[25]: 4 [! z1 B6 Y4 |- }# O8 @9 Z
    0    True( k) ]6 y- c/ F9 F' p
    1    <NA>, D0 `) U/ [* T5 ~  l
    dtype: boolean, i6 V0 ]: n+ W0 N' q2 K- \
    * r5 d$ W9 N$ M/ x- u" k
    1
    4 \7 ~! V# Z7 o  Z7 ?20 b. b+ K, ?: B) c
    3: d4 k# ~! k* O  f4 a, x' Y
    4
    : n3 p8 e% {) V# ]* u+ E  K5
    # Z# h8 c9 V0 e% y  U: `6 o62 [3 }3 j" r* b* u) Z; P4 ]
    7
    . k. P5 a. C! K, G. w, |/ X8
    " s4 k4 \1 x( j1 P9" ~7 i4 c7 m9 G
    10
    ' {. ^6 f/ G( Y- U9 \8 O/ a11. R8 I6 i2 q+ C: g
    12/ j" m; p0 z) J/ l
    139 y! Y* y- e: B
    148 Y3 ^/ y4 W( W+ V. ^
    15& H  t; [. S" P/ U
    16
    7 Q! P) D$ p  p: o+ u17
    0 R: T  d8 @% j18& w; }; ?% ]8 {- `1 D4 |
    19  i7 S1 q4 ~4 o& ?& A5 y
    20
    3 z' a* I8 H# h4 |21
    ( P9 m" f. H0 K/ w6 @* z22. y4 G% X6 A: z  V
    23
    + P3 h8 V8 J' E24
    + o0 X% O- ^% e: ]! c5 H6 z259 `; T) [( ]' j  E. t
    266 \1 X! Y- l- \/ W+ m' I
    27
    - U( ?$ p, I! d8 E" O28
    ) f% X$ _- O3 Z! d" w29* [! h; i" [7 _' F8 h' L/ _3 W7 v
    306 d  f& l; h- a- i
    31
    - \# T% `- S5 }/ C32
    3 Z4 |" v' L" ~2 @1 ^8 S33
    ) ^5 q/ M  g& R347 m- ?8 a7 p, N% D6 Y  u/ V7 f1 ~4 ^. z
    35
    7 e2 h' I  X4 U36
    # x" L' a( ?  E37  B6 v: K5 R( j7 w0 `: w% n# C' T( a
    38
    9 k$ w( p. B" _9 L39
    3 z0 o5 Y% x, D. r- K40
    , J; F2 V# v! R6 G. ~& b41
    7 u$ h! U9 F$ u$ A# b( ?42  ^5 D. b  P$ @3 w& n, S  e
    43+ C9 ?- ^! ?& X( t4 r
    44
    " S: }' S* W2 a6 o9 J$ ?45
    / G# H6 S5 S3 q7 ?46* T8 i9 Y# g& ^; w; b' l) L
    477 u6 K6 q! I- ?. c$ u' n' {
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    7 k9 V8 q3 y; V1 \" g7 |4 i* y  G3 o1 X7 C1 q1 H  Q
    s = pd.Series([12, 345, 6789])9 o8 T! r7 X) I3 M' j

    4 _4 K: u# n6 u$ A1 d4 z( Fs.astype('string').str[1]" R: ]( f  S; t; i
    Out[27]: 2 C& i! h% E, K! B
    0    23 r, ]' {1 H) p
    1    4
    / U* J7 P: L9 P. ^% x2    7$ r  {( J5 Y. T; F- \0 _
    dtype: string8 x' s% \7 K7 V0 a8 u" w+ I
    1
    $ ~/ I2 h% W/ x6 s4 |4 q2
    ) q/ N) C. ~! e$ ?3# o9 _( V( f, R& p3 D
    4" @4 N+ O- f/ X' V
    5
    - u1 i  o, w2 T, c7 m- r6
    . k4 W7 J1 a* W. ]9 j& I: f7
    - k, ]4 {( p  a$ L! r8  R6 J6 ?/ A# f( O, [, k1 v1 R
    8.2 正则表达式基础
    : Q0 _4 l! Z! l, I) ]1 J这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书/ v/ y' W, ?9 u

      S& Y, R8 q  @: G8.2.1 . 一般字符的匹配; o. H: [3 b0 D% z
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    * ^/ B$ s/ v. O: Y& G* f1 K2 a7 h# y1 k1 q$ k5 e& i
    import re
    # b7 O6 _! ]; q1 m
    ' F' w3 C3 \0 N8 _7 cre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
    + k; Z$ d2 k4 mOut[29]: ['Apple', 'Apple']5 A! r7 ]" }8 {- y' d. ~
    1$ X; H2 p1 C" L, a! p
    21 }2 D  t% ?, Q9 E
    3
    3 d. e9 P# `) }7 }8 `( N4; T* Z( b, w& ?
    8.2.2 元字符基础( |3 F- A. [& ^: \; V2 x
    元字符        描述7 S! M; d1 c9 M/ e
    .        匹配除换行符以外的任意字符1 o7 \4 F8 p0 A6 f; d3 V
    [ ]        字符类,匹配方括号中包含的任意字符" c5 l; m" u! }( I) p
    [^ ]        否定字符类,匹配方括号中不包含的任意字符
    8 P9 \6 }( p& h0 E& k; o*        匹配前面的子表达式零次或多次
    * x  z+ J% a1 v+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    " W+ J* ^+ w6 U) s! \6 j+ A?        匹配前面的子表达式零次或一次,非贪婪方式
    ) o* ~4 @* B' |. m/ l* N' X{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    9 d9 E' H: {+ Q9 v1 t* ](xyz)        字符组,按照确切的顺序匹配字符xyz+ E, X. n9 M- T) C# G
    |        分支结构,匹配符号之前的字符或后面的字符
    0 a" Q0 V3 z: h" t+ g+ i\        转义符,它可以还原元字符原来的含义) k' ?3 c1 Z8 ?4 C: i
    ^        匹配行的开始
    ! \# o% ~/ L8 E* S. f) Z1 L$        匹配行的结束
    - Y2 G  y6 W' [3 I6 o+ Oimport re
    ( g, x: [2 `( H$ V2 k0 e& ^1 Tre.findall(r'.', 'abc')
    $ \4 O4 l% {% e, z/ }Out[30]: ['a', 'b', 'c']
    0 i+ @& h1 O" X: c! ?* h
    6 K6 h' \/ S% A" ]' d% ore.findall(r'[ac]', 'abc') # []中有的子串都匹配
    7 F6 B; J0 [& b' h6 Y% mOut[31]: ['a', 'c']3 K2 y, x- D* v# I% B' e$ g; ]" g
    9 P" w; k5 S& Z2 z+ J( a; \
    re.findall(r'[^ac]', 'abc') ! h( O" ?" e& [8 ^
    Out[32]: ['b']
    # T7 r  b* f# ^
    1 ?4 t4 `! ?8 H8 r9 v/ f" c/ Cre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    0 @* y. B! h9 G8 VOut[33]: ['aa', 'aa', 'bb', 'bb']
    8 {% n- \/ }! t7 h
    2 z# \- O" K/ g. pre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串# y* y5 H: _$ E; p& U  B# J, t
    Out[34]: ['ca', 'bbc', 'bbc']
    ! [+ x  @+ |% c5 p7 L
    $ C6 G+ V( k" l# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    - X+ J; p! |! \4 ~% `2 I& K1 V"""
    - F1 u& J( }5 e# _0 @7 g1 U1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
    0 V: ?4 R# y; z/ F% ]2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边2 t5 h9 e- G3 q4 k5 U* e  U+ r
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,6 y; o5 C9 v2 P, G( N
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    5 t' ?4 J6 ^2 H9 T"""
    0 z$ O( p( o% t8 D" O: z5 @& e: c4 X
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
    , n4 u% T* \1 POut[35]: ['a', 'a', 'a', 'a']
    . H8 N2 s# i0 n' z2 i# ?. ^
    ; O/ v2 A& K/ Y) r# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。5 |7 T6 r# _- R: ?7 M+ i! `
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    . e5 ?( }& E+ V% {re.findall(r'\\', 'aa\a*a')
    ; r( E& x0 V9 E5 ?- O[]
    ' \5 u. X1 D+ v! `+ L* _9 @; F9 T! U' O* x, J% M, ~
    re.findall(r'a?.', 'abaacadaae')
    2 c/ o$ ^4 V) `5 b- y/ f0 OOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    ' E# f# ^1 A( f. G' \9 \; y+ Y, C/ p0 t* V& |. [1 s
    re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表2 K# Z3 S4 g# f/ L& L* G% |0 n
    [('width', '20'), ('height', '10')]& d' W5 H9 D8 C9 J6 ^
    9 m. r$ Q; @" B: a
    1
    ( }! k  ?- m& C" C20 Y8 A# f- K! {% M4 ]) [( K
    3
    3 d! E) ~& s& C, C/ L7 @* C4
    ( y  h0 ^2 w; @, w: ?5) O( J! A. \0 l/ ~* `4 |# t
    66 L" j& @3 n2 D$ p+ l% P1 H- X+ A
    7
    0 ]+ G  ^( q3 \. F8# B# \2 q5 }! \' {+ T7 s
    9- u) S, U. V8 g. k6 H, `$ ~& s
    108 s* j% Y- d3 I, t" r$ p
    11
    # E0 i- R  W7 a5 l0 Q12+ ~/ _5 ]; j$ W3 D
    13
    3 t: {: F8 B, l2 I1 w14
    # N! q5 E+ o3 f$ M5 v9 t154 @. z& z* L' q
    16. T) ^4 O/ W2 t' T, c) o6 U1 u
    17
    ; j+ l* Q3 K# ?* _* |! h- n, T/ R18
    % p: A# l3 q! N7 Q7 {+ F5 v0 \( ^19; m! U6 n; a0 P. o+ R4 _( \
    20* C' h* S0 F; ^& ~
    21" x$ Q5 \1 U. v( |2 N- ~
    22/ d  {8 W( E5 W
    23* c6 @  m* B- Z- E
    24
    $ X  J: J+ g" Z2 o) S" i1 A253 c& I) P. O! D' J, H
    26
    9 [8 }4 u% _  ]( l0 w278 o1 K) b/ r, U% V! [+ e4 D* e7 l3 M
    28% O* k7 ~2 h( _% p: J" L
    29
    ) O  H( n& }3 B0 u# z2 z30
    3 V" m$ E; X9 I7 g4 D: V31' |! O+ D8 W# o. c( u  \1 k
    32, L9 q6 r+ L+ V" N, X
    33' o% i3 E" j2 E4 ~
    34
    " G& b& x- f. M' N35
    9 M4 \4 i6 [! p/ N: L- n36, n+ |3 |" Y. V# u( ^6 C& I/ T
    37
    0 b: g5 ]" }  G: v) Y/ Z4 g8.2.3 简写字符集" d. S5 g* Y' Y2 D- S2 |3 w" o
    则表达式中还有一类简写字符集,其等价于一组字符的集合:9 N* S# A0 O" r8 _

    & S* _( Q2 m- z0 w5 Z简写        描述
    ' d  b# ]; D, f\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]1 X  ^" X* `: V# j9 g2 P, L- A
    \W        匹配非字母和数字的字符: [^\w]6 ]5 b( X' y$ i2 k
    \d        匹配数字: [0-9]' }* G& R* j! O6 l
    \D        匹配非数字: [^\d]' h! k1 d( g$ g5 f# m3 q) m" v
    \s        匹配空格符: [\t\n\f\r\p{Z}]
    * g9 V  D$ m# D' ]! l) G5 _\S        匹配非空格符: [^\s]0 J2 p2 j/ d; S+ V' U8 Q  f
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。) s4 g% y6 \# j$ P; C9 ]9 B8 O
    re.findall(r'.s', 'Apple! This Is an Apple!')
    0 G- i9 x* r2 POut[37]: ['is', 'Is']
    * B- S. k5 R9 C) j, q: M9 w
    ; {& ^: i/ s3 U; |. ?# {re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次/ d+ C& `( B* o! t) H6 ^0 D
    Out[38]: ['09', '7w', 'c_', '9q']- ]8 Y' ~1 ]# _/ X( _4 Z2 m

    # V3 J2 e" h9 a3 C# e1 y+ _" ore.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)3 ]) j, D8 Y) p  N+ Q4 a8 V
    Out[39]: ['8?', 'p@']" b6 m( o$ ?* Q' i8 m

    6 D7 v. q7 S$ Y! w1 S5 I4 ~re.findall(r'.\s.', 'Constant dropping wears the stone.')
    ! H. U$ Q. ~' H8 J; `, ?2 WOut[40]: ['t d', 'g w', 's t', 'e s']1 ~1 [+ A5 y  l: m
    & {# l0 B9 H1 S
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',6 N! z( k$ h' s3 |8 S: a
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
    - p) u6 s1 h. f/ q* o: |# Q  a3 c# Q- q, s! a
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    ' X. e8 n* m5 _% K. M0 @9 N2 x" Y9 @! q7 @  w( @
    1
    4 {; ]0 ]' J9 o& r; l27 X" R! i8 U% i6 N+ k
    3
    . R- M  _  A$ j$ L( ~+ N4
    ( e5 _1 [7 l$ |' E6 P5 ?$ Q5
    / t7 T+ [0 p' F# m2 z/ G4 X66 b' |1 `; K* p4 w' M
    7
    . Y. e  u* e+ X8
    / H& f) |3 ]. i9
    ' e- o: J* h3 R: ^9 v3 O10& N9 K, ?' {1 `/ H( C1 p$ c
    11
    6 U5 [# k" p# B" `8 M9 w12
    $ @, t. v! G( }135 l4 `9 t7 {& Z3 J3 m8 U- I, k
    14+ C8 X( _$ D5 u" s
    158 G" G+ T3 S2 k4 q/ f! w4 h6 N: u
    16
    & T4 h8 O+ O% r* N8.3 文本处理的五类操作$ a1 O! ?9 s8 y9 V( e! `" P5 v7 {
    8.3.1 str.split 拆分9 H8 n( H0 x7 M* @4 |( @% S
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    $ b. ~6 R( M# ~7 [$ L
    ' y7 S4 _0 d6 D' ts = pd.Series(['上海市黄浦区方浜中路249号',+ _: @7 y5 V8 h; F+ H4 h
                '上海市宝山区密山路5号'])
    2 W% `# h! {  Q7 m/ v1 S3 b& c, `( X+ z5 M

    5 X& [9 r* f2 U- ]! u: z4 Ms.str.split('[市区路]') # 每条结果为一行,相当于Series
    $ L" |$ @9 q9 P" \/ F9 W- H; g9 d% _Out[43]:
    ( P& x8 n+ @/ s, c9 _6 @0    [上海, 黄浦, 方浜中, 249号]; D$ r0 h! {4 N/ r  @
    1       [上海, 宝山, 密山, 5号]
    * T5 b; _$ C6 H9 kdtype: object4 K' O/ |+ j+ M: [
    # p; M8 j7 G8 R2 e6 r8 S, b8 J* X
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame8 f% i$ n/ X" B7 r- d6 x/ @" D
    Out[44]: : P: U( D& o/ ~, ~0 `% U" b
        0   1         23 Q. e6 j5 R( l* ~
    0  上海  黄浦  方浜中路249号
    + \4 x1 T: _2 b1  上海  宝山     密山路5号6 @, W. ^$ o: c$ ~
    1
    6 z3 g* {- A1 T. P# t' C: U4 l2
      R+ I: _4 f) m: e& v33 O( Z- f! K; z: z" v: U
    4. {& o8 ]' O9 N6 N
    5
    1 f2 U0 N: C, E$ F3 t. u1 W6
    7 |) y: N0 A/ K8 P+ w( y74 E7 {: l+ o# Q: P8 ~0 \* p
    8
    : C  h% j, E( E: v: d9
    - j8 t8 B4 ?5 F% u# [10
    8 d/ V$ |& L) H8 Z( m110 c; V, x: v4 N! T# @; K! A
    12. a; P: n/ W, f, p
    13( U: X2 r  p. e
    14
    6 V2 x* v4 }  G: S15. g6 u$ k+ ^, I3 n* T
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:9 i: I  _2 o, @1 U8 G1 w8 P1 ~
    ; |: z& ~! C# P' v
    s.str.rsplit('[市区路]', n=2, expand=True)5 f9 N4 K8 h" {  f* B; m3 S# y9 _
    Out[45]:
    . k6 U* O' E/ s. m                0
    % w, E% T2 b4 x  g8 {0  上海市黄浦区方浜中路249号! F! V* d- H9 Z" ~  t
    1     上海市宝山区密山路5号6 C, J5 t" @! E" _+ q" c1 {9 }3 X
    1
    . O' Y% w- j$ L2
    $ l8 @) ]; Z* ^' n& |3
    * v0 x2 G+ ]" }0 u9 N9 p4
    , @# Y- J5 s2 k/ S: e1 y# o5, H6 }+ p# E6 y/ ^5 k5 g3 I, s  ~: T
    8.3.2 str.join 或 str.cat 合并
    ( q( e  ?: c8 `9 r1 H3 h5 _6 g1 _str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。0 t2 G  X. k  ?; c6 ~& U; h
    str.cat 用于合并两个序列,主要参数为:
    ) i; g$ b' O% a/ b0 Usep:连接符、
    / o* C6 w; Z' L9 [% Vjoin:连接形式默认为以索引为键的左连接" s' W! z$ \) C# c
    na_rep:缺失值替代符号# H: K( v' G1 F- Q1 ]7 P% W
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    8 i3 D6 H' j7 N' ws.str.join('-'): N0 ^7 s' K, Q
    Out[47]:
    4 _1 N0 Q  X- {3 A2 B0    a-b5 c+ d) A6 X& j) T" s% B/ _( Z. w1 Q
    1    NaN+ o  r, I/ Q. v; n
    2    NaN
    / @8 U% O! T, idtype: object
    9 |7 r0 Q7 B2 d4 Y13 r7 q' J. a& s# y) y2 Z# c
    2
    7 W. d% p. i* G5 J" Q9 B3" l6 @5 T% Y! U  O/ Z
    4
    / l6 m% L, A- M* E8 M& [1 V$ q5
    5 v6 H" d- M6 z6
    - k7 x6 I6 W+ Q2 n8 a- s  j9 d7" ^7 }' L  M4 ?" Q" n5 z4 _
    s1 = pd.Series(['a','b'])
      f3 F( X" a$ y3 B/ Y& ]/ Y. ts2 = pd.Series(['cat','dog']). S' H  s6 U: I& o
    s1.str.cat(s2,sep='-')6 U$ J. B- L7 z4 V
    Out[50]:
    : |# A" Y6 _2 e+ m8 z. l0    a-cat
    ! `9 p. w# y5 d* y3 L1    b-dog
    / d0 Z0 g( v6 W" O6 f9 D7 idtype: object/ @3 _3 g, k9 r4 F% u* F
    : h1 H* A5 f6 n/ A
    s2.index = [1, 2], E/ x! @( e( k, G' P- w$ y8 v
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')
    ( |, C% D8 l$ Q+ W" FOut[52]: % N) _' J3 ?0 {
    0      a-?) y! \; M0 _3 }8 @  T# H6 A# Y$ n. ?
    1    b-cat' T6 D) C) Q# }* k# m
    2    ?-dog
    / l. b+ W9 ]: V0 F. Sdtype: object
    9 C5 D5 q+ T4 a! J% Y9 e. x# V  H1
    % O  q+ [1 m! V( c8 x7 @( H& V2  C2 ?; W! W) g& p  O
    3
    . E) h1 r8 |6 P# z4 g4
    2 t8 g  l" P. C+ t59 b% f1 {% D- t/ c' @$ B$ c
    6
    7 v. q$ K  Z7 K2 F! T  P0 f" N% \7
    $ w# b& m+ t6 g' S3 ~+ c8( `/ o6 |  c& ]2 o$ i
    9
    5 K. ~3 c2 v% H; e10
    ) K( H0 S5 R3 F& W- @11
    9 Q! q/ k. C3 a9 D+ ~8 f8 Y$ @& J12( D/ |; g& k) \% b$ g$ D0 N
    13
    4 x# D/ Q: Z, A; H0 D! p$ _% c9 {14
    $ M7 R! b+ F; q' ]15: [5 j- V+ e5 d" n& Y
    8.3.3 匹配0 q$ Y" r9 h- F2 S  G: i, E
    str.contains返回了每个字符串是否包含正则模式的布尔序列:
    ' ^$ n) c: r3 a1 ^' Ms = pd.Series(['my cat', 'he is fat', 'railway station'])2 [# M0 [1 E4 e1 L) q" d9 r- _, P" I
    s.str.contains('\s\wat')$ o! O' s! `- w  W3 N0 M+ M: e
    # [* R8 D. d( s9 G
    0     True& F" b5 @) E) _( d) q
    1     True
    5 I+ ~" V" H1 j2    False% ?& `! g& y, ^& H9 s2 p4 L
    dtype: bool/ @% o" ~- @3 b) M: @3 m
    1( G6 ^" C$ C+ |5 x. J; D8 ?) ?1 v
    2
      \) S; H1 L- j3+ o8 j2 k1 {3 X7 ?
    4
    # s% P2 V  C- I& g! V55 S2 ?# s0 b; ?- w- C* n3 L0 H8 @
    6, f8 r% b1 J8 I; O) |6 V; k) x
    7
    6 T; i" r' g5 I, e# b" C+ f* ystr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    + x& q, M# n) G. L' \s.str.startswith('my')3 A. j9 E+ M0 o8 r
    $ k; D5 x! C& c# t
    0     True
    . j+ _6 h& b2 S% s3 K1    False* {5 q  k" D6 a: F, w. ~7 T
    2    False
    0 Z6 s( Q; i: `% i  i8 y% z# Idtype: bool# C7 h/ L- _% }( _- I9 U
    10 g) B' e" k9 ]( L+ `: r# Z( K6 Z
    2) F  j4 |2 X0 m' p. V
    3
    $ O$ Y: E& m9 U" V% I4" f$ H) X' ]0 A$ p6 u1 |9 r
    5! x5 d4 b( L. A! X0 b8 d
    6/ T! [; L  P" o4 q4 K
    s.str.endswith('t'); u: c: R5 |1 O) H
    ) R& |: N3 B0 C: k
    0     True
    * ^$ I# ~2 q* D, Z! v: t2 q1     True
    5 {4 H9 Y6 ]* K4 X8 R7 a2    False1 [2 ^" M0 l5 {3 p
    dtype: bool$ G% U- v" Y- \, t  R7 q& z, L  V
    1
    0 I, j5 J% O+ Q. f" h/ L/ J- M2
    * E4 m: {* F. O2 M4 U( j$ T1 ?3
    ( V( q$ I5 m* [- P9 _48 `; {9 i; R8 M) n0 t5 C$ n
    5
    0 v1 Y4 h9 z- {' S/ |% u1 U, a6+ V7 `+ p2 B. n7 Q) A
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    9 u9 M1 c, ^& R; U! h- Ns.str.match('m|h')
    0 k/ w, x' R3 |+ ?/ S' U2 ^s.str.contains('^[m|h]') # 二者等价
    ( e6 G* [- S6 f3 i
      a% u5 F: f6 G3 T$ s* L0     True
    . K' t/ ?# c! T! }# S7 J; Z1     True" t* m- D, ?) D
    2    False
    5 p3 x' K* O$ w3 K6 a+ Adtype: bool
    . A/ D* m1 |9 w2 w7 |! t8 X% J1
    # \2 R& G* I: G: \22 A) m1 C( {1 T
    3) `4 `7 e  X  }# A) `
    4
    1 V) x$ e, A9 J0 u# N56 q7 p+ ]1 ~3 t1 v- d+ m- {
    6* c: D- y# Q9 f  O: R( B( r
    70 X/ ~+ j& S& @% a9 `9 Q. o
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配- ~! g% ?/ w- a  ?9 {
    s.str.contains('[f|g]at|n$')       # 二者等价; K) k1 j* D: `- V8 X. N5 x( [- h3 J
    / _) g$ Q. j$ `8 ?
    0    False( f* B* n, L1 n- l
    1     True. r9 E7 }  `9 _" A
    2     True
    6 s) Z3 v' H1 J3 sdtype: bool3 k4 V+ j( a( `( m
    1
    . ~3 I) J" b  ]: j. c9 M2( H) l% S# o% B3 S5 Y+ e
    3
    ; Q: i- S% b( Z6 C4 z3 p8 H0 [4
    ) ?3 x7 [6 f+ E9 T! A5
    % M8 @* S) ]9 T: ^' @' V1 k5 G6
    ! F7 I/ o& u1 P5 n; z% x! D6 z  ^74 {9 S) I! ~! v* O
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    8 q& O9 {# K- r) q/ O+ U3 Os = pd.Series(['This is an apple. That is not an apple.'])
    * }3 s! ~+ u* R9 B# o/ g6 X0 G! @$ V! t% i0 K) {) Y
    s.str.find('apple')# s) W( L* p3 R/ P0 K, L
    Out[62]: : F* ]8 A/ j2 f, o; |2 L5 p  K
    0    11
    ' {1 s8 i6 ~9 {0 `( f8 idtype: int64
    ( ?( y; I7 n9 x' {' k; }7 i. ?4 x
    s.str.rfind('apple')" C  L2 F# _: c; @( |
    Out[63]:
    8 n$ M1 o- c( u, A4 h( g2 J' }. n0    33
    , c1 ^$ {$ l  i+ [" w3 g8 B: h- [dtype: int64* f, B7 I5 F( q+ q) Z2 j
    18 e6 m+ a, Z/ P  g& o9 {( \
    2
    5 r% ?3 V) r( r% q/ \3 r: E33 N! K7 t  b8 b3 n9 z
    4
    * A' u5 p0 y8 N, O) j5
    0 U# T; ?; h* Z8 ]1 |6
    : r, K! r/ K: X. R9 [) e& S/ j4 n7
      t9 W' K- Q9 T; s8
    + C3 r& B3 s7 g# W. d9
    2 _2 W" a( i! @, j10+ K# z+ x7 I) t5 x3 l) M) t
    11
    " D9 f/ y. N" a( q" q9 ^& @替换
    ) K# h# z. m- f) Gstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。- d9 G: Z( L1 k. x( q1 I- v
    s = pd.Series(['a_1_b','c_?'])( p  X2 b5 B! D8 L2 n. U$ s
    # regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?3 D( t; r! d4 T- e. b
    s.str.replace('\d|\?', 'new', regex=True)
    ! r% H* x8 A" i: y9 ?8 K
    ! O6 m% [6 x3 }9 O) m2 H0    a_new_b
    $ o3 i( n* J2 F6 }8 P% f4 Z* w1      c_new4 F/ v! Z* T% _; u3 L/ i% Y
    dtype: object  C5 }9 o9 m6 C: |3 A- X3 [+ r# Z
    17 O" f, A2 @! L( x% _
    2; h% I! w3 {# m2 l
    3
    ) o7 }9 l4 @* J: ~& d4
    4 [6 X1 v3 q7 ?* [3 Q/ O& a8 G! _5
    2 p0 k1 n" p: S63 F4 {; o9 ^& P5 i
    7; \; a% r6 H/ N" N0 f8 D  o
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    & ]6 I5 E/ h8 R4 U* o* z: `  i! m; c$ w' m6 @+ {
    s = pd.Series(['上海市黄浦区方浜中路249号',
    + V4 `- G9 t1 ^5 G& [                '上海市宝山区密山路5号',, A7 ~$ O4 r* j* s6 ]
                    '北京市昌平区北农路2号'])
    3 I7 y# @' ?2 a1 {( ^pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    + [* z4 |1 `4 b8 Y6 ^& ^6 scity = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    1 `% v8 l' g, b. N; mdistrict = {'昌平区': 'CP District',
    4 W4 K6 ~& [" Q& e            '黄浦区': 'HP District',5 V- s% J: N' F5 M! O5 A& {$ q' m' ?, a
                '宝山区': 'BS District'}/ {5 H/ V) W2 n7 w- l$ F& |) \" ]; i5 A
    road = {'方浜中路': 'Mid Fangbin Road',; \5 ?; B6 h% o3 ?7 U; Y
            '密山路': 'Mishan Road',) U( r! E0 {! y. ~4 V9 C
            '北农路': 'Beinong Road'}+ u* ?# t  b5 h8 a! p$ E* i1 m2 g1 |
    def my_func(m):
    . e) y$ ?% R- O: i# _- W    str_city = city[m.group(1)]
      @9 _: J% z. G4 s8 k    str_district = district[m.group(2)]
    " |1 X' k+ H4 [8 H( m% E8 R4 g* C    str_road = road[m.group(3)]
    : l/ h5 i" W  k2 c    str_no = 'No. ' + m.group(4)[:-1]
    6 O* E, v% I0 n+ _    return ' '.join([str_city,
      D+ _9 U0 U9 {                     str_district,
    - s/ d) l/ T- V" A  N" n                     str_road,: u9 k. ~9 H) q8 R1 f7 U
                         str_no])1 u2 e2 p$ p2 i' e2 J3 g
    s.str.replace(pat, my_func, regex=True)& k, v+ b' y5 e* V

    4 C! H; n2 p; C0 i1
    7 Q2 Q9 t% M& K) v% p/ t* [29 _; k( Q; Q: a+ i0 h0 e% ~: [% l
    3
    3 C& E, c& L. R. q/ |: w. r4
    6 `0 F8 w, |2 e$ [3 D* ?) Z5
    * F7 g1 h' z6 ~1 c' l0 ]" ]$ N6
      r7 w* E* ^8 y0 H( u2 v9 Q9 T# e7# I+ ]' ?6 P* W9 T( q
    83 ]8 h) X4 n- s( J4 A- O, w( v! G
    9
    % n  v1 K+ X' l5 E, s10
    4 K4 o" A3 P! p11
    $ J( K9 M3 O+ m" \9 m4 W3 [7 {" Q121 j8 k8 d+ ~' h7 P- c7 \
    131 E# }1 g- O  w  F7 R+ o
    14
    ; _* y- v8 X6 ]2 f15
    ( {5 _# R8 J' }$ W16
    " u; m; Z9 _" l& |& X& x: p0 K17
    # ~1 j0 C, F9 k- J9 J$ m18
    4 c9 n, F7 {/ a0 k$ l3 w19
    + Y4 Q4 h; P* ~) O9 J1 n# H7 Q; D20' N; {) r% _- ]
    216 l! A3 D& N% g4 |. s" i
    0    Shanghai HP District Mid Fangbin Road No. 249, M/ V5 c2 ~0 i; D
    1           Shanghai BS District Mishan Road No. 5+ f0 T/ g3 x9 T& u7 H2 w( |
    2           Beijing CP District Beinong Road No. 2
    , Z  H9 Q4 {8 F9 I7 x0 Idtype: object9 ?. _8 v  v3 D
    1
    % x1 |8 X/ E3 U$ v8 l; I' q2
    ; a' f# O' a9 c0 Y# ]' P3
    , P" Z, r7 B$ H* G7 L47 y: N0 ^. N  ?0 [  D2 [% b8 @8 g
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
    " I6 g; b5 r! d* N1 I& l' {& U+ v' {
    # 将各个子组进行命名
    0 J+ O1 f& w3 n  U$ I* ~; Upat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'$ G# b2 H9 H2 C* U0 F' T. p
    def my_func(m):. X% w9 h& S  U
        str_city = city[m.group('市名')]
    ! K. N% F# o( q6 q8 b    str_district = district[m.group('区名')]4 [8 O0 x+ {# @, }- N) Q+ O
        str_road = road[m.group('路名')]
    2 @2 w9 V5 P) z- r. h    str_no = 'No. ' + m.group('编号')[:-1], O) x* H" s' \3 s8 S  P% C# d) u
        return ' '.join([str_city,$ \9 f8 P+ P+ D" A/ ]4 m
                         str_district,
    0 _& D! u! l! z* ^2 d6 x8 |                     str_road,
    & s1 ?- C, U+ p- Z" E6 ^2 l                     str_no]). c8 t) t4 ]: S% x+ C3 f
    s.str.replace(pat, my_func, regex=True)! P$ f: B! I6 B! I
    1' {' e% c" B+ }+ j3 X5 B6 K* e, X6 K
    23 m9 N4 K3 S6 y* T; N. y7 b
    3- Y+ D2 @$ h1 k; X' T# R
    44 f- h7 ]* V' W- ?/ W9 v6 j) @5 @
    5
    . e4 A( G# a. x8 }& v$ c+ O' |9 N6
    1 S+ ]. Z. a( k7 L  o2 w/ U* L+ P7
    : A" [* |  x- }- y/ [! n1 R8- w: M  j8 e! s+ C- N+ [
    9
    6 E% R$ G7 A) @5 n107 X$ R3 t- M8 v9 }' t# j5 L
    11
    ; T9 x! ?, S0 K/ O4 p) M9 c& v12
    6 K$ Y9 R4 V" Z6 V5 z0    Shanghai HP District Mid Fangbin Road No. 249% K; Z- Z0 {" y4 ]) q
    1           Shanghai BS District Mishan Road No. 5
    " q9 x0 W" }7 m3 h4 M2           Beijing CP District Beinong Road No. 27 E$ J2 y* l$ K5 l: F
    dtype: object
    2 e; n. _* C4 b4 ?/ E: Y3 L# D10 P, m& Q3 U. z# e6 ?; }+ {8 I
    29 h, k$ k; F, a% C9 e) y
    3% U4 P* W9 W1 P6 Q/ \
    4: ~, P% r0 r( `
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。0 c0 I4 N0 X7 z2 p. K0 f
    ; t6 x6 l4 H( p1 f- @" L
    8.3.5 提取
    7 G6 L& t& C# n2 J$ N  U: \' Hstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    / H4 y! p* s/ {3 }7 d3 |5 M6 N3 D( is.str.split('[市区路]')9 Z  l5 M. j* F; }
    Out[43]:
    : l/ x0 b; E& D% I9 b6 d! r/ @0    [上海, 黄浦, 方浜中, 249号]5 H$ P1 S; o5 {# D0 E& Q8 M
    1       [上海, 宝山, 密山, 5号]
    ! k3 G$ r% p. m3 ^( ^dtype: object5 |$ }6 z7 O+ o& j3 T& {! t3 W
    / c( e9 B2 X5 ~
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    0 C+ v% @  d; x: K$ Ls.str.extract(pat)
    ) F* Q( H# P8 D; W) v! D: aOut[78]:8 a7 ]/ U" N5 x6 ~
        0    1     2     3
    ( c/ }! Y2 h$ ?! E/ E0  上海市  黄浦区  方浜中路  249号
    $ q$ \9 c2 Q" `% Z8 v) O1  上海市  宝山区   密山路    5号
    : J9 G. L' H, H' {7 E: J4 Y8 i2  北京市  昌平区   北农路    2号
    7 e% S/ A  ]$ V9 k, F1
    . [/ ]# z8 M) a1 I2: n5 n- n: l; H7 U) s& N
    3
    9 M# _% M2 X: d& e! U2 `4
    & {  `+ @  e6 D3 o5
    3 R5 s1 f) |' g: V8 ~6
    0 h% Z( Y" P$ U4 y# d3 }5 m6 P- q7' O, L& {7 S% H( S% o7 {
    8
    8 x$ X6 G1 g7 O4 R+ ]- q. {94 j/ l6 _  t# w  i  V8 |; ?% w  n% [
    10
    ' B7 d" m7 y7 L6 A113 |4 W8 s4 o) x. l( E0 l6 y7 t
    12
    . H) S9 }/ o( R# t4 {137 q8 B; m) `- T% K
    通过子组的命名,可以直接对新生成DataFrame的列命名:  Q$ l5 ~# d0 m- {
    6 P! `% P  i) i0 }% ]( T: m; Y
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    $ J8 @6 v. w$ o. C! Vs.str.extract(pat); @2 a9 t- o! Q6 D
    Out[79]: ( U0 X$ Q# I9 n2 D$ Z
        市名   区名    路名    编号" o2 T$ z5 U) @/ @; O* w8 s
    0  上海市  黄浦区  方浜中路  249号  O0 d5 X! u% |3 O( ^' ]
    1  上海市  宝山区   密山路    5号
    / J3 a; u4 w3 S( S2  北京市  昌平区   北农路    2号
    2 A9 D' c% \! j+ d1& i2 R, D; K& V! O7 [* z0 k. R4 ~
    2# m1 o8 z  ]) I1 V4 u
    34 E, v' }4 x  Y1 e; h) p6 e5 ~, O
    4
    8 u3 T& G, O% _% l1 y; B% Z- q53 {1 X, Z# E3 B5 `* }
    6
    0 R! i6 M8 i$ W+ l+ g7
    ' b* h" f$ q( e9 w5 P* N! qstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:$ h4 M/ [/ w0 @' T; }+ S" T
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    6 O$ i( `1 T* ~6 @, Q% dpat = '[A|B](\d+)[T|S](\d+)'
    ) `$ Z# V' r% ^: {1 b. Ss.str.extractall(pat)
    - x3 i. P* ^, q: \2 H* q7 lOut[83]:
    ! N$ E3 x* v5 Z       0   1$ [% ~4 c5 E8 F. m9 G; x7 t
         match         
    . ^/ S8 T% ?5 x: [2 o8 {( i/ imy_A 0      135  15* g0 N5 c/ r6 J. E1 k
         1       26   52 J' T' C" n2 f9 C$ q0 l
    my_B 0      674   2
    . C3 ^! L' y& F% T7 s     1       25   6
    0 ]+ P* a/ N$ b* j$ o10 p$ w, g# U+ U7 D+ n
    29 j& u; z% q5 L. z1 ^7 F" y; D
    3
    1 l# H. Y; W8 C3 F47 W! j# y0 K7 v6 S. Q2 I
    5
    : s! M. r' D0 u- |6 |6 S9 K6
    ( ]- v7 x! B( A% L. L0 N7
    + l5 Z2 \9 g# `' A8
    & s  [% V: w% \9
    4 Z9 A5 N; p0 x3 P10
    . l$ S% a; ^8 z: s  Mpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'! E- r7 X7 w/ x& D& Z: o2 f8 a
    s.str.extractall(pat_with_name)
    5 p: a9 t. d$ [9 t. F; E# J0 L: wOut[84]: 4 u) O) U* S  L  {7 {
               name1 name2
    8 [; m$ U* N" o. l' t     match            : h% ]+ a6 L7 u7 r4 o) A8 z8 w6 h' c8 L
    my_A 0       135    152 A: T5 T+ G' {" N
         1        26     5
    ' @* q7 E- H- b( fmy_B 0       674     2, M! D* G1 u: z' h& y
         1        25     6
    # W6 {/ ^% w) b. Q: B" _" \1& U' H9 {. p$ ?3 S: `' H
    2
    # {9 d3 i" m2 O& S) E3$ R8 F. k& D! e/ ]1 @4 I$ J
    4+ ^4 x& _4 k8 v4 h
    5% T* k. s; ^+ q
    6
    & E6 b& B9 |+ i+ b$ ]+ \7$ x, Z) `2 N& ?  Y3 a1 `; ~+ u- y
    8
    ; ]) r) R  X# [0 |( b9
    ( }/ t/ F4 z& F! [+ sstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。* g; A+ x5 Q8 n, F9 U2 j, E
    s.str.findall(pat). M, n4 x5 f- ^: k! \1 N. @
    1
    / z7 P* t! w' O. F& b2 |) D- d/ E8 l  jmy_A    [(135, 15), (26, 5)]2 _0 s8 z& b0 [+ U4 i- K
    my_B     [(674, 2), (25, 6)]+ W, t: \* k6 a3 M4 `
    dtype: object
    . t3 C7 m- d1 l2 W1
    / J5 `4 I( x3 W2
    7 R, Z  H0 A0 p) {3' Y5 ^; a: v0 \/ O; z) q2 ~8 Z/ U
    8.4、常用字符串函数
    1 r& X" O- K6 o/ O5 ^  Y! X! n  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    5 b, d1 M3 o6 K8 w/ `' ~3 p' H: g2 m9 R3 W
    8.4.1 字母型函数; Y. i% [, _3 Y3 m
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    & ?" |5 O) Y3 L% p) E$ l7 J7 C: z5 U5 {) E- G% ]! C: y7 @: `6 n+ F" C: H
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])( X7 O6 ~% W$ Y

    " d5 b; F0 m% ms.str.upper()
    " I  d7 b& l8 R3 e% r* QOut[87]:
    2 B  c$ o- q$ \) b  c0                 LOWER. ]; N5 [- C$ U& ~/ n, i3 j) D
    1              CAPITALS6 _1 \- {! k) r0 ]' f0 ?
    2    THIS IS A SENTENCE+ C0 Q2 T* b: r+ V, S
    3              SWAPCASE
    . _7 m0 i0 d/ h& R) K% edtype: object# d, Y8 D' ^) B! \- C
    ) ]( r: M! C7 ?; ^8 T6 O2 l
    s.str.lower()& ]4 [6 c2 h$ x& U' r
    Out[88]: " N, M. F1 U+ F* j, l, y
    0                 lower
    ) K  T* q# W$ g1              capitals5 h, n! l4 |0 g3 F4 B5 o
    2    this is a sentence7 Z* z% T0 q" P
    3              swapcase0 i1 w; {0 a! m3 ~7 B
    dtype: object. x0 U  y  j7 E! j

    / j' Y* ?  x( b5 _) Fs.str.title()  # 首字母大写
    3 B8 f+ m) Y: c" UOut[89]:
    ( K, z* x# e& ?* J3 h/ C( G0                 Lower8 S) W* M7 @4 u( m
    1              Capitals
    ! D" Z* A, G: r' y" C2    This Is A Sentence
    ' I# [  [/ G- ]* R& ]$ J3              Swapcase+ C9 P; _" d# T4 @; C9 Y
    dtype: object
    " }6 ~2 B, D) S  b: j/ E" R5 w8 a# ?4 j( G2 }2 ]' W6 F" b( F
    s.str.capitalize()  # 句首大写
    % H8 Y- F0 V! O% [Out[90]: . r! K6 ?# H; Z
    0                 Lower; S  Z! S" D% X0 t" V' I# p
    1              Capitals
    4 z6 Q/ `; r/ u4 Z0 B2    This is a sentence4 [' i# a" Q0 w+ m/ Y% y5 M+ w! [6 _' y' K
    3              Swapcase$ R) D! p! x( k& P: m1 ]
    dtype: object
    + V1 G/ d* h% L9 ]) H2 t) Q( Z+ ^4 ?% q8 `
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。( N5 l0 `. h9 E. J
    Out[91]: * G9 y/ k' ~/ w5 |5 O4 M
    0                 LOWER' [2 h8 y, t# C' G5 J
    1              capitals
    / M$ a2 o' m( R9 A, I2 L2    THIS IS A SENTENCE
    - {; Y* m% _9 V: g3              sWaPcAsE
    7 Q3 w; ^' y8 r4 U. {2 `dtype: object
    0 }% E8 _# q- f* g# r' _  h( S8 O1 w9 R2 p2 T/ n
    s.str.casefold()  # 去除字符串中所有大小写区别
    ' p* P& L0 h; X" Y" N5 `
    " A, J' d% k6 P& l$ A0                 lower$ e0 E1 |* E0 `( M: Q% e6 i
    1              capitals0 v! I' w, s. A+ R* v
    2    this is a sentence7 w8 t* G  c  U- p
    3              swapcase
    6 X! @8 w9 J/ z6 }; N' g
    ' X; b! C4 M1 m' S" H1
    $ _/ @, d: |/ h( w2
    ( i& s3 d, o$ p1 r! C3
    - {1 G# J$ M) l  i% r6 z2 v8 u" z4
    " Y2 f) J* ]' U5
    4 K# i1 m, J) F: ~) ~6
    $ h8 r( x/ h+ h7
    ( {9 B: K4 E0 J6 E- O8' S) s6 f* G1 G' n+ D  z
    9( h4 p/ a8 |1 X- h& B% _& I
    10
    ; Z7 t( p$ b  ]0 |: X5 T9 O; @11
    7 }+ L: i3 F$ o12) ?5 ^/ [' G2 r! m3 T
    13
    8 X: j2 o# G) d+ s& O% D, h14
    9 \0 E! O9 t% @9 [2 |15
    5 t; R0 J2 c, G+ [1 I% P) F  s16
    2 j- K: W' O3 u7 J* H/ Z' l17
    & h+ }+ l. ~, f) p# k4 \, e18
    ! Q* a8 D6 Z' y" @9 v7 ^, }: ^190 e: H) v; F8 m3 e
    20/ T. i" t2 j! D( `
    21
    7 e+ |3 F2 s1 D/ }$ p22
    % z8 I/ K" G$ [1 x$ ]1 _" t, C23
    " D8 [& x7 \2 g: l24% R* o  D3 c$ p# x6 P( v. M
    25
    5 B6 ^, B3 [6 c; l4 u8 l26
    5 [$ O; }: `2 w3 R27
    2 y! I! `  C' G0 r28
    * T0 x# [  T" A3 T& N29
    6 n0 o& Q4 |5 G305 \- u- l2 p* h% m0 H0 G. I! y
    31& M' q7 n' n1 h8 p  \. b
    32: P+ C8 p- H. V; z
    339 @! o% |! j! D! c" ]
    341 m# B: x+ w1 b- {* n: h
    35
    & X5 X2 z/ \: y: C6 V36
    ! K8 E3 w6 C4 t1 ~, g1 d- @37
    - _( O! B( E$ R3 b38+ u# I  G! \3 L* e0 D
    39) ~$ b- m& ^1 }, W# n* b  d
    40" `* Q' q) `5 h3 `
    41
    # J/ l+ Z: t2 Q/ U" o42
    " d6 |7 U( j- Z! t/ ?43% q3 d5 Z% x) t( K( M- u- b
    44$ P+ [8 ?- q4 o- W8 F- n
    45, q; }( j# k& e$ F( G( R
    46
      C  J6 ^5 h/ I4 Y( `47
    8 T9 S' F! R) {/ M* \48! H, R  p1 }, N1 ]' V
    8.4.2 数值型函数
    ) P/ K0 t  e3 F  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:6 `# J% T4 w/ o) M9 t

    . M: G# S7 _. r' A) Z& j  h4 A3 Zerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:" G4 d, p* U- R9 \1 i4 f
    raise:直接报错,默认选项1 J- i. ^: f0 W  u
    coerce:设为缺失值
    : {! K+ _% P% {& r8 ?; {/ S, h, P- {9 Bignore:保持原来的字符串。
    # o: F$ x% x* @& d; m' qdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    # d; h6 K9 X( t% ^6 rs = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0']); ~/ B6 A: x  U

    8 D' S1 g9 c+ N' ?% }pd.to_numeric(s, errors='ignore')  k- j: `& ^' {- I. T
    Out[93]:
    $ C6 R) T2 N7 f7 P/ \$ |* ~2 x0 @0       1. @5 m' J' U0 ^2 D
    1     2.2
    1 z0 j8 z6 q9 ?" r2      2e5 ~' X- K7 P. g( [* N9 R
    3      ??8 |- p/ y- _# T5 Z9 g; s. x
    4    -2.1( q& M$ b7 t0 N1 l
    5       05 |/ F( e; X8 c
    dtype: object
    : v, s0 _1 V* l" f1 v( z7 h3 X: D* A9 @5 Q6 P( a4 g8 W
    pd.to_numeric(s, errors='coerce')" \- {+ U1 r1 T1 @. i
    Out[94]:
    $ ?" |6 P# |3 A: y1 s4 Z8 M$ b9 J0    1.0) c7 X; X, ~7 R7 L1 [2 e
    1    2.2
    4 N$ \( c. r7 U8 J0 n2    NaN1 ?! ?. w  g4 ~) ^3 I
    3    NaN( b0 s) I; S- |4 m! v& _1 A
    4   -2.1
    4 t/ b( c( e; R2 S2 q+ @" v# J! o8 Z5    0.0$ l  u# M% r- k3 V! m& r" Z+ g
    dtype: float64  S/ a: f- X* U! `

    3 S5 x; [$ O. P% T2 D7 a9 I$ I1. Q: J- a; k, K1 t: ^2 b+ f
    2& z3 T6 K) a$ T, _! ?2 D( M
    3% H! \$ x/ a/ s
    4) s4 X/ T2 C4 L1 @! [. n+ Y7 @
    5
    9 o- T) b6 _' L% s7 A/ L6
    0 R" o. h6 h: O* ^7
    1 |! a; s* ]" \4 M8  j9 p% G& `0 w' a
    9  T( d( [2 T  A- d9 Y
    10
    " w& n; C+ l" W6 z8 ]2 `11/ ^4 I! D0 X! }- r
    12
    & D' r9 @0 L7 ]; B: w136 m- b7 K  |. e5 N
    14
    ' t0 n% O: H0 x3 E' b- s15+ w, L& f  ^! o7 R) C& V0 m' n
    16
    / W' G9 x" T8 g2 V- \" m17
    7 I, n& d: c$ X$ J* G6 m6 S+ d18
    # m9 t3 A" q4 g3 u4 w7 i' M19; Y/ t3 \% ?" @# M( l0 t, v! a
    20
    4 d/ S8 Y& [) K. S9 v9 K5 H21
    # e+ y  F' @4 f5 _  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:1 N6 k6 o8 X9 Z$ N& c8 \+ C
      o, |9 {2 }# b4 f+ z
    s[pd.to_numeric(s, errors='coerce').isna()]
    9 \* `: O' k6 X7 hOut[95]: & t& l. ?# v! y# G1 V0 Q, N0 B; `
    2    2e
    8 E0 b0 S  o+ ?2 Z2 t1 x3    ??
    , i2 w$ k( j, Z) Rdtype: object
    7 n5 n8 }) x$ q& o1
    / A' D9 ~. w7 A  o2
    + Q0 h" {. n8 K! A4 h0 b* S3  e4 f5 k. |5 h
    49 e, p  Y& m; C# f
    5
    " R) h$ ]: w2 f5 v* e4 v+ R  `/ T# K8.4.3 统计型函数
    2 i+ M3 b$ m8 m/ |: v  count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    9 [& s6 L, @7 `/ R6 T8 I9 w" ], q5 M6 a, R
    s = pd.Series(['cat rat fat at', 'get feed sheet heat'])/ v$ Q" \% w) {# f& Q

    ; m' j2 l" [3 qs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
    3 A; d$ C0 h0 OOut[97]: 5 I5 K4 W3 U0 R4 ^( o
    0    2% \, Y- |0 ~9 B& d1 A) B, X
    1    2
    4 N8 |' A/ d( `" }" ?: hdtype: int64
    , |) J, d9 K# X) E
    5 D! Y7 j  [+ F  Hs.str.len()
    ; P, r7 F. g, r  q. S1 ~" hOut[98]:
    8 y$ P2 ~: |' A. H/ ?  i" {0    143 C" y* w+ E& n+ ?7 S% T* k
    1    19
    . v* s( }. T3 C7 g& _# L2 Q# udtype: int64
    0 i, Q; w. r! d7 S1. ~9 v7 M) Y4 U8 @2 m/ B
    2
    8 e! l( g2 \4 S+ f1 N# D9 k3
    ; T/ ?' d  ]6 |6 G5 u/ s+ p48 q0 O  D  l9 ^0 |- z  F4 G4 E8 F
    5
    # U# H  G, C9 @3 Q& \5 K) l3 v6* |: @$ D6 |* `9 D/ [+ ]
    7
    2 M1 l+ {# S- o: K  O8 y4 m9 _85 F% m# ~, X7 d6 Q# B7 o
    9% v6 E- Q/ w" l6 @
    10
    # @( ?! Z' \# y7 w, v3 T11
    # x- G# O5 k: u- `12
    7 l3 g" s' }' \) Y7 f13  o" v3 `3 X- a
    8.4.4 格式型函数
    / q! {! u$ j6 p  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。, U0 s3 t6 t1 B) X! t

    " Q" s& I' m5 `* f+ Fmy_index = pd.Index([' col1', 'col2 ', ' col3 '])% {0 _" t  y+ ~) u0 i2 j6 m6 ~% m

    9 J4 p! k0 p6 p! G+ ^- C1 fmy_index.str.strip().str.len()9 a/ c2 a  D" N% s* h, n, {
    Out[100]: Int64Index([4, 4, 4], dtype='int64')" S6 t- m8 K$ ]3 T  n9 [! M

    / v9 W* j! H3 m% x* J1 {my_index.str.rstrip().str.len()! E2 @; _2 b" z7 h
    Out[101]: Int64Index([5, 4, 5], dtype='int64')% m0 \' w' c/ m# [! S$ ^

    0 U2 j, f0 ~# a; F3 d: p9 jmy_index.str.lstrip().str.len()- B& g2 T* @: _" v( b) h6 B- V
    Out[102]: Int64Index([4, 5, 5], dtype='int64')
    ! |: z2 F& f  [& j1 M0 V1
    & |1 |4 O2 f& ^2 |2 d2/ Z5 ^+ ]' U% f+ l. G$ m6 t3 N
    3" Z1 a' `% V1 V' O9 K" |
    4
    ; y; z% R. Q4 ^56 E2 v0 h" C2 ]8 p0 z
    6; w% B4 j$ h7 F* U- l/ s
    7
    7 M6 _% E, o# D* y8% U2 Q) r" ?( ]# H. ~; d" s
    93 O7 K2 M! ?" c- ]) L8 o
    10
    3 K# X9 V- N  ^9 _: j# S/ u  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    3 e" c$ A: U2 I3 D  C! Y1 U% `. ~% H. ]9 e8 d) l9 s! T8 {
    s = pd.Series(['a','b','c'])
    % i% e& q' r9 h2 A: \. y* R
      q) e! T. g8 fs.str.pad(5,'left','*')) C8 V6 S; e$ {1 I8 C: I% Q. ?
    Out[104]: : V6 S1 y5 M( U& c$ R5 h/ G
    0    ****a) H3 V) @6 L/ _
    1    ****b6 c9 F) x( J! p* L& C9 q
    2    ****c, ?3 E0 E+ U( ?
    dtype: object
    7 ^7 F# L. D. P) e( Z4 L7 }; o
    8 d7 ]4 @1 Q; ^1 |# @8 @- gs.str.pad(5,'right','*')$ f7 Y/ v/ |6 L, S) }
    Out[105]: ) v: O( K& t, ]
    0    a****
      `+ }" K8 L+ r8 B% x1    b****
    / A8 K8 ]$ ]/ x* Z" i- J$ M4 f2    c****- Z1 i" A! C4 g" C' R. J
    dtype: object* c( p2 W' N8 Y( L) A$ o& ?& U

    6 Q2 ], V3 Y; ~s.str.pad(5,'both','*')
    0 h$ L% s7 k! C3 j$ yOut[106]:
    * B( `  E; o; A0 f0    **a**/ P; C5 f* u' T% o! w
    1    **b**4 j( [9 Z0 y/ d) f
    2    **c**
    6 L, O, E8 |5 v' Q+ Z" {dtype: object' u& o% J* _' ~' D* \, m

    + C' b5 ?7 T2 k# N* D: t1
    & F( d6 n+ L* J/ ]7 K27 u5 O( B& R8 l& U% Q& I  R5 s' C
    3  ^) F. f/ e* Z  y* R+ Z
    4: e( @8 `& I4 Q
    5
    , `. i8 C3 b6 F/ |* A  q) Z6$ b) o7 R7 c% O' a
    7
    ' n+ D4 O+ h3 ]2 z$ {% U8
    - ]: [6 Y/ E7 n& l3 Y2 ]( A9) X! y' ]) f9 b% j- [
    10/ G; K! S4 V$ y# M
    11
    3 C$ `) V7 t, P; o, `129 h, i+ @( `2 [* Q4 O
    13
    6 x9 L1 ]: G4 A6 ^9 L14
    6 V: B: Y  Z+ I( q5 p/ ~/ @& E* }0 l15. K1 b! J' k  X1 n4 m, ^
    16
    + y: I: _. x( M. o% I* k17
    ' C$ P" q+ ]3 R( D18
    - h( c4 H1 s3 N2 k- H" L4 ~+ c% m/ d19+ n0 @' u( u. m3 l7 P
    20
    % _' d" R3 [& J' \3 w, w21& i( g, a% w8 o( t& ~: a! j$ O
    22
      u; {% {  L' _/ p( J$ F1 \  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    ) o" B3 {( S' w; g4 d6 `
    6 m& L; m$ {6 Y, Ws.str.rjust(5, '*')
    ' v7 k6 E7 C- G9 cOut[107]: & `) |( H) I+ F! U8 O
    0    ****a
    6 ]6 i8 a2 B' B* [) v+ \+ W1    ****b
    . l/ [* B" n8 Q0 X& I" m3 J' b9 @2    ****c+ b) [. j) V+ K! @& U, D
    dtype: object
    % n2 ?$ m% E$ a# h3 v4 d) Y
    / s8 ~, e, I( Is.str.ljust(5, '*')
    8 u5 x. B% N0 _! V+ g: SOut[108]: - ~" V5 T4 H; j% [, g( e
    0    a****6 z  h7 b8 K7 \0 d) Y4 `  g) o$ m
    1    b****& U3 w5 ?  w+ [
    2    c****
    % f3 X. T4 g! {3 Fdtype: object) `9 A. T$ P" E5 F1 f8 b

    9 Q+ ]1 w; B2 D3 x- W. z* L6 ls.str.center(5, '*')* x9 |, B% P) e6 q
    Out[109]:
    1 w' v1 G8 z% j4 U. s: U* l0    **a**9 y+ \0 d# n% B  c
    1    **b**
    6 t. i) J8 b# A6 o# S" H5 W8 e8 w% P2    **c**" R/ k7 `3 a6 f4 h' [8 z
    dtype: object
    # C# b9 t3 v3 M6 X1 H) p$ Y3 w9 ?/ T8 I+ u6 H' q& v/ v
    1' U( Q7 c4 z2 [# T: K
    2
    , @1 Y2 A; d+ G0 x3 k% ]3. Q. S8 E2 U, [6 |
    4
    " e3 Y9 o& d6 a4 j) i) O0 G5
    9 L! M" [5 D0 f9 r64 H8 X8 W9 W5 ]7 v' S
    7& U. k9 `& r2 u$ ~5 r+ G
    8
    / M# |5 y* Z) Y9
    3 ]! n, o% ]$ A4 D1 m0 k# W10
    ! g+ |  D# X& v, G11- Y' k) z8 P' R. X
    12
    $ M" r  u" V7 K! A0 C. m5 m131 p/ P4 j. U! C9 p4 T" R  Z
    14
    " H+ N( }6 _1 V1 U) W3 I  A15
    5 h, C' ?% p# o+ @16
    7 b1 n" }/ e( E( T4 y5 r17# ]3 t8 W+ B2 n- ^, S$ W
    18# N5 W& g; j+ K4 Z1 Q$ l) E
    19
      x" D, ~' s% f0 ]6 Z! m20# {/ R2 |/ _7 K- O) |
      在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。" m! Z+ U: y1 A2 @4 S' w
    8 F6 m  C9 F+ \) ]
    s = pd.Series([7, 155, 303000]).astype('string'). ?4 }/ W0 d- [- T

      R9 Y7 p, f, s( @* s. Q/ e/ f8 bs.str.pad(6,'left','0')
    7 s% f0 O8 @8 i; IOut[111]: 9 I6 j, P" U  H, w( x( T
    0    0000072 H' l8 C3 }0 v+ u- \2 p7 W# o
    1    000155  k$ I. J9 C: d
    2    303000
      A; P5 X0 ^" Hdtype: string( z# U7 W6 C( q) l; f+ R4 S

    , e0 ?0 E5 N5 l! x: Qs.str.rjust(6,'0')
    0 F: Z+ _! I1 A5 uOut[112]:
    3 }% D, T9 T- M- G0    000007
    6 x4 X% _' y. m; q7 c8 s! S% _1    000155
    9 M( V& B/ _( H) V: p2    303000! O7 s4 r/ b' _, ^3 Z( p
    dtype: string
    - P2 L4 N& f0 m) j6 i4 q4 V8 C3 v& {- u, ~$ x- z5 q
    s.str.zfill(6)
    0 p/ i* N( H2 P8 W) ROut[113]:
    6 B+ I' f( i# o6 d6 l0    000007
    8 U  ]& s  v0 N! F5 B# p1    000155
    . V+ U% X, C7 \2 ^  |+ W2    3030008 M" M, _7 l1 `$ o
    dtype: string# W# g* N8 F2 e% `

    9 {% U/ G9 K* D% H; g, ~, ]1% [. w4 \! A+ p3 V7 U
    2
    ( y* @# _* h. u1 e( L; f3+ v& x9 V3 y# R& [4 S; P! i
    4' j) d0 i4 c% ~( G
    5
    8 j' x8 |( a) H# F2 t* J# ~6
    & z6 E3 A! D6 Y7, }- a2 P0 s! c6 P3 x. W
    86 ?  O9 _# m- o& t
    9
    , i8 {; _- y$ y& J( A: e% V! A* X! s102 n4 M+ B: I& R) n6 w
    111 J3 m! ^. T. j' O4 K8 a
    12
      O/ P, ?; a7 g$ D3 Z3 V" u13
    - {) l5 w/ ^2 {3 ?14
    ' C& j* E0 Z- K" g15$ i- T9 v" f. k# }% P, c
    16
    3 Z- E# j4 M. m# F+ D# y172 y+ `, l/ V& s# {7 r' f
    18
    1 b/ I6 s( F0 a3 |/ u1 z2 G6 _9 Y19$ k0 }- F0 r9 `! w% M! [9 q9 _. S
    20; C- L9 {2 j5 v, D& l( c8 m4 ^1 [6 o# Z
    211 H/ X7 w1 w+ d- E2 s" o
    22
    5 I# v# G) f+ _+ b8.5 练习. q8 H) r$ G) G
    Ex1:房屋信息数据集2 B" s$ C5 |" I4 W- m
    现有一份房屋信息数据集如下:
    $ w4 _% q  p: W* t- q6 \+ `" [2 l. E9 j# a9 e
    df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
    ! o3 o& g- k* u1 F$ |df.head(3)
    6 V6 R3 \6 P8 L$ B" w6 d3 a( yOut[115]: ' U8 |" p3 W( I( L
          floor    year    area price# g) x+ a! s$ X3 L
    0   高层(共6层)  1986年建  58.23㎡  155万. B, {2 o$ a% ^
    1  中层(共20层)  2020年建     88㎡  155万
    6 k" w* q: O- r: J' _' w2  低层(共28层)  2010年建  89.33㎡  365万: o- a2 V2 |. f8 n$ z
    1
    ' J8 [( p2 {& x. O2 s3 |' m( h0 C2
    9 X* z, l+ H  i/ Z$ z$ x5 @6 L3
    ' Y9 O' s4 d1 F  F$ H0 f- g8 u" V4
    9 P% x/ j/ R) T% ]- |5& D2 u) J0 M. S5 b
    66 |; g$ h; G+ D: J2 x  Y2 L
    7
    : }5 H% c) a; G  \. u# w4 t将year列改为整数年份存储。
    0 N' i2 K* V8 L" _! ^将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    3 Q# ^# Z2 s5 X8 g' [计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    - `  r, N. t1 R, l将year列改为整数年份存储。
    % f4 D& n+ a- P4 r) m! m2 B3 A1 Y"""
    0 _& J& U, l( F' v* w* B2 _整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。1 g+ ]& ?, R( r5 v
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    , \$ I3 B  V3 w% N; g4 I* \' |4 G( `转成int后,序列还有缺失值所以,还是变成了object。/ v- J9 P: G/ V3 P: C
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。! w4 O) o" L4 Z0 n4 A4 W; k$ `
    """
    ! B3 _( ~4 j; [9 j! q% d/ ~df = df.convert_dtypes()8 F6 j" [: Z+ J: Y/ J5 Q: a: x
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    ) K& D9 h8 X7 }; v& T- Odf.loc[df.year.notna()]['year'].head()8 o; I1 r6 |; F3 v" G
    ; E" \! C3 k- h+ A- t2 I
    0        1986
    ' k8 @- `( d4 n* @# M1        2020& W' s8 |- f$ p1 [- E# o7 W
    2        2010- b1 |( C  R8 l6 k- N( p5 C
    3        2014
    ; M) l! f& j. X& q2 f8 r1 {; w: l4        2015
    # t2 h9 L9 v+ f! P8 t5 lName: year, Length: 12850, dtype: Int64
    + P5 N) @2 {6 r0 c! k& Y3 h$ |* B+ F) P. ?/ k5 R& ?
    1
    3 W8 c. M- W& s! O2
    , W) _7 F; F2 e+ z' P: r+ E3( `' q% ^2 C- }! Z
    4
    $ p5 Y3 G. F3 k5! ~# H* A* w, Y9 k/ ]8 ]+ ?
    6( |7 |5 G( D, O: ~  `! X: k
    7" W+ z; p. c0 A) Q- t) ^
    8
    2 e. K7 X: }" C; M9 P. t( y9& ]; N4 O8 D( {1 K  @
    10
    6 ^0 n# S1 K4 Q. p# X11  @( H1 V5 W* n/ ]' ^
    12: H2 M0 k2 ]9 ^9 V0 j
    13
    / h5 D2 c( F' j: k14. e+ i0 u9 Q4 W8 _; k% _
    15
    , v& ^9 W8 l0 O/ u9 U4 n8 O165 P) }8 ]+ a) Q/ {: o
    参考答案:
    ! Q4 F4 E4 Q+ N+ q. j- k! A- j( q9 t
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么( k2 ~7 G5 j5 O& M

    , E$ p) a/ k4 Z& n$ x6 w2 gdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    2 j: |9 s: D  }( \! ]; Xdf.loc[df.year.notna()]['year']
    , l! o7 i+ P$ x# }3 B1+ v9 p. y" |) @5 H1 b. p
    2
    & Y% j6 J2 e5 U5 R( e( \* q将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    * s' O/ P. B" ?2 a! c" k4 F3 @pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'6 H( R" \6 [, d7 V9 W
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    : _! T0 d: g6 }df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    4 M( I& s7 K) j7 l3 R$ B2 S; idf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    0 [$ v, ^) z7 a* ^! x$ J: Ydf=df[['Level','Highest','year','area','price']]0 m" [7 N: A1 e- D
    df.head()
    % V0 |' M+ ~0 U" u! n
    . e& b5 D) x' \1 S% M/ b" X   Level  Highest        year        area        price
    6 @) s6 a8 o' D, z1 [7 i" f& J! Y0        高层                6                1986        58.23㎡        155万
    / x; i8 O4 C0 s' a3 D& I# @0 d) a1        中层                20                2020        88㎡        155万$ S1 \- Y' i" b5 ^, k4 q; `
    2        低层                28                2010        89.33㎡        365万' @. D% ?# c& D# L; P  k, V" f4 U/ \$ N
    3        低层                20                2014        82㎡        308万/ T6 ^' [4 U6 w6 w4 W# n; _- \( T
    4        高层                1                2015        98㎡        117万
    9 X4 z* h, Y; K3 J: Y1
    + g' o7 P" [3 c8 r+ D29 d' B9 [7 _5 [. n! \
    3
    ) p0 _% t& r& Q) C7 b4
    1 M* b) j( o& a+ R) v5) l, H( o( {6 K: b2 j7 J1 J9 b
    65 f% H+ O# S- X6 ~4 a9 M& M
    7
    5 U9 q0 m0 A& [; C) t* F) Z8
    + p, Q( ~' Z% N2 A3 g9 }$ }4 i9' f$ O+ f5 d/ P  s
    10
    2 g, c, w  J/ T11
    * s8 Q, o. C/ a6 U& c$ |- {& q0 M# `* \120 w" d. X% m' P0 n
    13
    1 W. Q8 {/ m6 s* `' j/ y; _( [# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
    ) T5 Y( K9 {9 t* [pat = '(\w层)(共(\d+)层)'
    : J4 H  T; U( C( K5 F' D3 D# Cnew_cols = df.floor.str.extract(pat).rename(
    ' U1 K( l! `. x; w, F8 C                    columns={0:'Level', 1:'Highest'})
    ' B4 \# S( p; y$ e1 S8 b3 ]
    ; |5 v2 U1 G2 V/ k# hdf = pd.concat([df.drop(columns=['floor']), new_cols], 1)
    % F7 @8 y1 t1 |8 F7 d+ U; Fdf.head(3)
    9 I9 ~8 B3 s4 z5 K' g+ L6 H/ ?  ]  n2 E% V' N" O' J% F
    Out[163]: : f# \7 g, J' F  A
       year    area price    Level Highest
    7 {! w; f4 l9 O* ~) u0  1986  58.23㎡  155万    高层       6
    & q+ K, a, f* |1 `: E, `7 x1  2020     88㎡  155万    中层      20
    6 J: i1 ?6 W: Q& j2  2010  89.33㎡  365万    低层      28/ n0 H; T4 b2 p3 n6 ^7 A' S5 F4 u7 @
    1
    ; B: p& [) d! Y5 v5 u% d27 C+ w  }, x* h- T2 E/ u% @
    3
    $ _/ K; \+ }4 a# U: ~/ B: s  D8 V4
    ) B3 x  o% U! ^5
    . t, p+ K9 p6 w6 k6
    5 y$ L3 M. C' C8 D4 ]7# A2 p; z$ {4 E  W
    8
    ) j3 N9 ?# Y; `7 @, V, _3 a9
    6 O5 B: K( ]/ `+ Y* g* M: n101 ~- _6 S  h2 U5 ]. _& ]! F" T! W
    11
    & P; w, |9 z8 a2 m12" \9 a( ?) y1 p, D% X* Q
    13
    # p+ u! X: e6 E$ i4 s计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    2 F0 W- w: z! \* D  }7 m' \$ d1 _; `" g"""
    ! B- x; V& _% {6 e% Q( }# Wstr.findall返回的结果都是列表,只能用apply取值去掉列表形式
    7 j: M& R' a6 J6 X* k4 `参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    . }2 O: a5 ~/ d2 E由于area和price都没有缺失值,所以可以直接转类型
    0 Q; Z$ D: h) U8 q- i" O9 z# r  D"""
    ' A. o' t3 r& ?# K9 `* ?df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))6 N' c* k4 |6 X7 S+ A
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64'). y& o) W3 v9 B' N4 s
    df.eval('avg_price=10000*new_price/new_area',inplace=True), Y  ^8 q# j  |# f
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))7 z1 O  i/ a  |: i4 ?8 v3 U+ x
    # 最后数字+元/平米写法更简单, M3 o7 m- s5 ?0 w* n% B
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'. L: h  `5 S9 O+ h, r6 o: a; I
    del df['new_area'],df['new_price']
    7 `( e" R! i0 `. `4 ]0 f+ Adf.head()
    8 w! T  j0 t& {) I" A
    2 z5 W! A1 x: r2 g; T   Level        Highest        year        area        price        avg_price
    - b5 f; h" g9 ?1 Y; l6 }( ]0        高层                        6        1986        58.23㎡        155万        26618元/平米
    ( z, A$ {; f* i3 ?  A: x, v" R" n1        中层                        20        2020        88㎡        155万        17613元/平米2 L& ^# e' i6 t& Y) W6 r2 h
    2        低层                        28        2010        89.33㎡        365万        40859元/平米
    ' u% I8 C- z- [4 F# R3        低层                        20        2014        82㎡        308万        37560元/平米
    . L- T8 |7 i" J9 N( A2 k: x4        高层                        1        2015        98㎡        117万        11938元/平米
    . s5 [: c2 Q6 V  G2 j+ W  Y4 _: T2 \' {* x2 x9 w1 k4 A7 D
    1  ?& h0 R8 @0 F
    2: ~. \1 X" @& k" |4 `# q5 u
    3% f& _- a1 X  \9 Q
    4
    ( Z. m/ R- `+ L1 h% Z0 e5  Q3 y' ]* o  x" y
    6
    2 n. m  h/ I0 l2 J. c; p7# ^  T+ G- S; z- s/ U
    8- b) I' ~7 K; v; z9 I- X
    9
    * k( G: B+ j- [10
    1 {, p1 V8 e+ c9 w2 t! }- X0 B116 ~' P' |1 r( I7 V7 U
    12) D; C; h* s. f$ V0 F2 U
    13  F  L- l6 _/ _# @1 e. w8 N
    14
    , o2 V6 \4 k7 V7 x15
    & L, ~/ Q. W7 l" P; T" @16
    ' z5 L2 O  B+ M( i1 {# r17
    : I7 H' x/ f1 N18
    8 d$ K/ Q4 l' s3 k- v% Y190 C0 H( ^8 p5 f) b& f
    20* |5 N4 k1 b( x% D
    # 参考答案( V9 R# o, A$ x
    s_area = pd.to_numeric(df.area.str[:-1])) I+ ]* i" Q5 R0 `1 c4 h! m
    s_price = pd.to_numeric(df.price.str[:-1])& f/ o" b: q2 ?
    df['avg_price'] = ((s_price/s_area)*10000).astype(- W) N6 t* x" _; e4 T
                        'int').astype('string') + '元/平米'
    9 w7 q- z+ o1 J+ ?/ B/ J
    $ \2 E" O# ^1 W0 }$ h2 Ndf.head(3); l+ g+ C: z# s+ ?
    Out[167]:
    * S, q6 x) [% b- l+ x   year    area   price   Level Highest  avg_price5 k2 [3 C- t) w& C& Y! l6 m! O
    0  1986  58.23㎡  155万    高层     6          26618元/平米! @. h. l' J& u3 y" e2 z! F6 E
    1  2020     88㎡  155万    中层     20          17613元/平米
    + L# w/ ~/ [0 {) i2 d8 y2  2010  89.33㎡  365万    低层     28          40859元/平米6 d' s. h6 P9 y  f
    1! Q" D: P& F1 J9 Y8 b
    2) B. b$ ]6 n- A/ U
    3
    % R: [  M- g" g4 W  e9 _4- A$ P! }) G5 B# F
    5
    $ X) R. l" M2 v4 k$ j) C/ j6
    - `( f) g. `. ^6 c0 {7% E* F1 G1 Q7 I# ]# L' c1 V
    8
    0 T, w' {0 [3 g0 x2 l97 v/ }  D9 i6 {0 R# g7 u- H9 g
    108 l+ p8 S# \9 t8 _( v1 ]
    11) A6 M: i+ Y! h/ ^( I5 F( k
    12, A, ?" @9 _. d0 ?: l
    Ex2:《权力的游戏》剧本数据集- y1 g$ A% ^; @& S& A7 a
    现有一份权力的游戏剧本数据集如下:
    7 _) q7 p& g  }/ T$ w2 j2 V: y0 b* m5 @5 a9 W5 V
    df = pd.read_csv('../data/script.csv')
    1 C) {: {1 S1 g6 u- g) v6 Idf.head(3)8 E3 c6 s0 S4 Z# a0 K6 D
    & f3 j" K$ n! Y+ m; O
    Out[115]: % s% [- s3 L5 A- r8 G
    Out[117]:
    - t: [& H8 O: e' C4 `$ P" D  Release Date    Season   Episode      Episode Title          Name                                           Sentence
    6 B( C' D$ w) Z7 r- j# g% j0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    * H$ s! Q- [; K/ |9 ^1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...) a2 N. b- L9 y; f
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    ' f+ O7 Z% \8 c' |2 k1 ]9 c1, E7 ]' \: |5 W
    2
    : I3 A: x! V1 g, `5 a, K  i9 B2 i3  _( T8 [9 h3 i3 Q5 Y5 v; q5 p* t* U; W
    4# V- l6 L" |% q& T
    5
    6 F# }# \) w1 s3 i6 @1 Q4 g: S6
    3 v: v$ Q" k& l( i- g7
    0 R* ?4 j- l- `: j7 B' O! Q: R8
    5 g" {1 a* W# i! c* C2 z. Q9
    1 n* f* |  J/ G  v计算每一个Episode的台词条数。
    9 L+ n" N+ m6 \# S3 L' }以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    - H. W; g8 Y+ z若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    ( r" u- q' j" K, N6 G! p9 j计算每一个Episode的台词条数。
    7 c5 r4 `9 s/ W- k4 ?. Ndf.columns =df.columns.str.strip() #  列名中有空格
    % _# C8 \$ c. ldf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()& `7 [, T1 T( O! ]3 F2 G3 i1 B, o

    1 v) l  [6 {2 i: nseason    Episode  
    1 @% ], V, l+ b7 v: Z$ s" ASeason 7  Episode 5    505
    6 j* V$ L! b" W* y. F8 \Season 3  Episode 2    480
    $ |2 |7 @+ x3 l4 S3 ~! i! FSeason 4  Episode 1    475
      {! Z; }' ~4 r! |! t3 PSeason 3  Episode 5    4401 N8 z4 H# R3 C  r
    Season 2  Episode 2    432- i1 g0 b1 ^4 z, \( n7 h
    12 b0 s; G+ ^2 P. i5 a9 S5 ^
    2: Q; C5 r- X/ I* c
    3
    3 t7 k- R1 U9 T4& n. S# w3 P8 B5 f& ]- a+ S# X( m3 S
    5. n" k3 t+ f6 Z! r
    6( n* J1 {, i/ r7 n* H
    7
    / ^5 h) r. a0 F; y8 Z4 {- X8: O# a  l) G3 H% Y2 \
    9
    ' R! A3 ]( r* U; k" `) M" V+ |以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    ' W$ V5 D, T0 [: b# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
    - y; P3 s2 X; ?+ mdf['len_words']=df['Sentence'].str.count(r' ')+17 O# i- ?( S1 R% C8 Z5 z4 }0 [
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    , N# E; o( _% i: a3 N# x4 A6 I
    . b% k5 B- o' }: c7 G% w' nName1 B0 Y5 V6 M7 R# S4 z7 s4 ]
    male singer          109.000000
      v8 o! v! G& l* [' j1 J1 kslave owner           77.000000
    , h, q3 Y) o% Y# @6 B' M& cmanderly              62.000000
    # d9 h9 z4 D+ }4 b% [( qlollys stokeworth     62.000000
    $ w9 L, k0 l$ _! j( Mdothraki matron       56.666667( V; s% w3 L; w' H' N8 z
    Name: len_words, dtype: float64" N( W& r8 I; n9 C% q& k6 P; X$ g
    1
    : G; I9 y- L+ z% R0 d2' H. P9 o/ O' N- c3 R3 p# X
    3
    & d- o; y$ G7 a4
    8 G# E" X5 e! i5 h5
    3 m. n6 k5 v" p( p6
    4 h% Z2 y6 W+ E) @( |7
    , S: c  Q, _. T% l8 C! c$ c8
    + r, U3 S+ p! P) B9
    , C* O2 d  d' ?, u5 K6 V104 F% Z! b! U& R) Z+ f
    11* Y# r: o( N8 \3 V# P" v5 G' s" x9 B
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    / s3 Z# h+ V! E2 b1 e! Z. w/ _df['Sentence'].str.count(r'\?') #  计算每人提问数
    ' }8 m3 X, J) z! S- j3 @ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0, o6 {1 Z. ]/ n/ i9 H2 K# e
    del ls[23911] # 末行删去
      Z8 X' U8 r. X" xdf['len_questions']=ls: R3 A1 o3 H. n
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()! f% T8 d4 Z/ X5 M6 `0 `
    2 |5 ^1 \- I/ O
    Name
    $ u/ S' c2 s' H) i- D1 atyrion lannister    527
    / G$ y5 {. @( c- Q& a- `7 f  Zjon snow            374" m% e# e# g: i: N- z& }! \$ @' D
    jaime lannister     283
    4 h! E4 [: I6 W6 [) R# warya stark          265( m  u; n+ m" j" Z; Y+ k$ o
    cersei lannister    246# r3 J' Z8 h( [. q6 z6 K) U5 Z
    Name: len_questions, dtype: int64
    8 X$ |4 A1 O, v( K5 [. Y% M! f3 R# N7 U
    # 参考答案
    4 r+ K) J# n2 @s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    / u( E- n( E6 n  Ys.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()+ l* `& w, K5 e  @1 J. [# L
    4 G7 K4 M2 s3 v% T; W
    1
    + g2 q+ Q: z+ C2! T  Z9 d) i* V; k' c, s: E1 C
    3* \1 c$ [" C1 E
    4
    7 F' u5 p% E+ a7 O8 h5
      C$ c" I9 @6 |7 [3 y6! j8 [2 G! ^9 u
    76 B2 I. `0 L. I# u# A& M
    8) v/ q# J0 E* |9 _/ e5 v0 `  [: S
    9) W* N' W7 X" x0 H1 ]
    10
    ! E% H- ^/ c; E+ U; l11
    * q4 x$ |6 m* P2 _9 G12, @% D8 U: I% j. m) b- a7 V7 t8 c. l
    13* q4 |( f0 d8 ^$ L) o/ l6 a
    14' Z4 o* X; x% j" w4 k" _2 O' r
    15  E3 I: M6 k4 ~5 I2 ]) l8 J
    16! Y$ O2 R4 J, K% j
    176 |8 k+ o& v3 E! M
    第九章 分类数据8 q/ f4 G9 g  ?- ~% @2 m2 H5 Y& k
    import numpy as np+ ]+ x$ H* }+ U* |
    import pandas as pd- G# t$ i# W6 F$ }) @
    1) G) c$ {( f# T, t0 l! h$ F; [
    2
    $ p/ F- l9 l' o% _; W5 g9.1 cat对象0 r) {! @* {3 C& u! W
    9.1.1 cat对象的属性
    ) o3 @: u: w! p! O) V: V  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。) k& X2 M& o1 C$ L: u2 ?
    - T* ^1 J! d( s; c
    df = pd.read_csv('data/learn_pandas.csv',
    / Z: M! X5 @# L5 j     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    9 C4 t4 d$ s) p" ^0 e& is = df.Grade.astype('category')# [# J4 }# j% C( o; `" _  ^2 N
    , Q% D+ s* E. y+ i/ ^; b! z3 z; E
    s.head()8 m% F! x- Q/ L
    Out[5]:
    & ^. J; [% I: _" t0 `/ h0     Freshman
    ( X: ?. |+ s/ e! b' u) L1     Freshman; P/ j! r( _  C; R
    2       Senior
    . [# P/ Z- J" }+ v+ s3 O# M3    Sophomore
    ) [6 [+ v- {# i, `4    Sophomore
    & q( R/ F" @' D7 A6 N( n, b* c7 xName: Grade, dtype: category
    " F2 w8 o( i# Y! WCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']- I/ j; H9 @8 t! k# o
    1
    # R' y: m8 q0 {8 [% X2
    4 @. c7 m2 \% g" i$ L3
    4 e0 Z8 q! i! t4 u4
    8 M$ P% d7 u2 n' T6 s& n- T) T' G5
    : Y* n  P) ~# l, O. w0 U9 V6* ~1 u( p5 r) x, C' P( U
    78 h- |# p# \) M. {; U
    84 `& u  c2 E: H& Z' f$ o/ R
    9" p: _* G$ L0 s& z; r, L$ t
    10
    5 E5 y4 U5 n) J% }* O3 }$ o11- u5 V6 ^2 ^" u
    12
    ) E% v# |' a& s1 K7 j. r0 H, B13) K' V2 h4 F9 x( |+ `/ S/ i! {
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。! T2 ~/ u0 X$ ?7 w0 @/ N4 M

    5 m) B) u( S+ |& P, N9 ]s.cat
    8 z5 Q% r5 W2 c" P! oOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    ' {* J& Y$ y1 x8 T/ }% d1. k1 g: P/ n0 h4 @
    23 n/ a/ e, L  ?3 y: Q- Z8 ^
    cat的属性:
    7 I2 }3 B0 Y  Q6 U9 w
    ( p* U3 x7 P" Y0 Q0 d! rcat.categories:查看类别的本身,它以Index类型存储7 ?" Z: T9 R" `# A. H, p
    cat.ordered:类别是否有序, \, X8 s- z$ z$ K. Y
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序7 Q, {- L9 c4 i( s; N' G) r, c
    s.cat.categories
    + Q" p  _! r1 y( E0 IOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
      E8 p+ S: w" d: ?+ I8 p& t3 R! v, \" W/ v! \; g3 u
    s.cat.ordered$ \7 g! i8 j/ f* a5 A; k* K+ e
    Out[8]: False. h1 R3 X+ B. F2 p, [- g7 u, L
    8 \- I! `; T% y/ S9 p$ ?
    s.cat.codes.head()
    / s% a% H4 J4 @7 k" C" DOut[9]:
    - f6 c$ M! m0 c& `2 u) e0    0
    / ~0 N3 s" r$ Y4 y2 U: V1    0
    2 o) y1 i" v4 u5 s, h+ i2    2- A4 [, o* E) z* Q
    3    3
    ) X1 |" V+ Z# R, l4    36 z( Y3 Z' e, i) r! ~
    dtype: int8
    6 I* |6 {* ~, \5 r6 W6 N' o$ n1
    2 U$ E+ {" z. ]2% @  c' o9 ^+ n, f1 ?
    3+ e, c5 Y( z' t4 E1 A
    4
    ( n- L' r  y, r. d; L( ?5
    $ Z; g( u7 t. @* s6
    3 r7 C+ n4 B- c% F7( R) d5 x+ [# ?1 v+ s
    83 `( D8 [3 |% Z2 L9 |, `& g# E
    9, U4 @/ C9 \$ R8 I3 Q
    10; ?+ f/ n9 _) R2 Z) S# E
    11
      v( E  i6 L' a+ ^12
    ; i- P3 z# W2 s( }13
    3 t" `6 @9 f  I; H14& l& N1 L% R% z. A
    9.1.2 类别的增加、删除和修改
    " }: O1 b) _; s4 r, m! _  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?; I1 S  s6 ^. d% m

    & |+ Z( w4 Q+ W3 z  E9 r2 Y【NOTE】类别不得直接修改
    + Y6 C; w& w# D. d在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    6 M+ Y2 {$ P7 x" W2 u
      y& g+ m" j2 U# L" f! c; E6 radd_categories:增加类别2 O3 i0 J8 f3 E/ {: J3 r
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别  `% I+ v4 Z# N3 ~8 i
    s.cat.categories; S: {7 q/ p: o- b/ v

    / ~6 p  F/ }: \- H. F' BIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    5 S) O/ i5 N( ]- ?# r3 @1
    $ c: w/ V) W9 I1 ^- x29 R* {1 p" L* b7 ?0 F; g! R% \% W; n
    3
    3 M7 b$ d3 L- ?$ K  L# y& C4  Y% g$ |  M9 `( p
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    9 v; V6 e. r, J3 B5 w1 Ks = s.cat.remove_categories('Freshman')
    ; f) _* V$ {5 P' H+ t3 r; M9 [* l9 X) N/ c6 L; S; Q8 C
    s.cat.categories+ D* n3 r- S; ^" U
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    , J$ \, `& a$ W! U! R, }
    " V8 c" w( j3 t1 {( ns.head()( _2 g4 p0 T  e
    Out[14]:
    $ i; P# S1 j# j4 K, q/ e0          NaN4 {3 u( D" P! u9 O3 i$ _2 g
    1          NaN  i% I. s' P0 u* a3 q$ ^8 g! [. g
    2       Senior
    $ Y; g' @# H  f9 Z, G: }3    Sophomore- B0 w& [" Q  [* U
    4    Sophomore
    1 c2 T7 _0 p9 |1 W" |Name: Grade, dtype: category
    * I, L% A4 ^9 P% \3 ^Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']4 v  c+ p2 k7 J& y9 g
    12 ^3 M) J' V0 P  q
    2. z2 v9 A8 V2 S4 d. k' a
    3
    5 E0 }) R0 Z, {1 e: x$ ^% J4
    ! L! v0 N0 S; r: n7 V5
    , M# [1 u; Z+ D1 X6 s6 ^6
    4 _0 s7 `! g3 H- K7 }- S7% \3 G' z# T) e" j
    8
    3 ?! z. y) U% L: j1 U95 R2 o7 l& A6 u2 m# R
    10
    6 n, S9 m; O+ |; T+ `4 V# ?4 D11: L9 A1 j3 H2 ?6 E
    12
    + k6 y$ x7 L6 Z/ ^: U7 C! S13- P/ ?+ A# n/ G1 D* X) n+ h* |8 D
    14# I! h9 u9 n; D! r4 J
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。% {3 Z/ L5 \& S! c2 k
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士0 W1 y4 u# ]* \, }2 Y$ K- H
    s.cat.categories, N. e0 V0 z+ ~  h! c
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')7 O, T+ O4 f) R% h

    9 y+ r' c( c8 B  ts.head()8 S0 x# ^+ C- u# ?
    Out[17]:
      ^: @* A  I( D# p. f4 l0          NaN: }8 Q6 n0 d% p6 c
    1          NaN6 a  Z) t& I9 z6 Q
    2          NaN
    - c$ t; e7 H+ u( |3    Sophomore/ h9 |' H' S! ]) b, R; h5 B
    4    Sophomore
    ; i8 f9 J. L* B- pName: Grade, dtype: category
    + ?/ i  j1 k* A$ i6 fCategories (2, object): ['Sophomore', 'PhD']+ T3 U: c7 X/ ~! t$ i3 T
    1
    1 h2 e  E9 }! P$ \2
    . R* V% y' n+ A# V3
    ' k/ Y/ S; Z0 X/ y8 v" g) J49 L/ ^0 [3 O; B9 k! b
    5; n0 g) ^" ]7 U' u* W
    6# x. E+ B7 }% ?% B3 a
    7
    $ @2 S0 a+ Y3 l  |+ z8
    7 ?4 B% `( ]3 B/ M% P+ `5 P4 G: i) U) y9
    + F' @1 f7 `8 C5 b1 H# H10
    7 O9 Y1 q0 `+ C+ z! q- x/ _116 D6 I7 Q0 e  V0 Q* ~- e
    129 R' T( t# M/ @( x) Q* v
    138 S8 ]. X0 I2 b! v7 n/ @; u: f
    remove_unused_categories:删除未出现在序列中的类别
    ' I+ c* ~* Q- S. q2 d1 l$ is = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    6 }# Y" x2 y+ B; n# p% Ys.cat.categories
    - |: d0 b# Y. V, l2 b  ]# E& [: Y% _" i' R) G$ R( G. ?
    Index(['Sophomore'], dtype='object'): w5 n% x) h; }2 |
    1( @; v; v) L6 W/ Z
    2
    ' G. x+ r' l4 q+ ~7 m3' h5 {6 K+ b& g) O4 v+ R) q+ ?
    4
    4 l0 y8 m1 f( \; I* ^rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    ; g) [4 }( M4 i  z% D: x& Ys = s.cat.rename_categories({'Sophomore':'本科二年级学生'})) O& T1 V! Y* e
    s.head()
    ) R7 P# k. U3 n- d0 V
    7 z% \; w5 a: r" y0        NaN4 W6 |. _9 H: Q0 q* P
    1        NaN
    2 t) n: y6 B5 K/ |: \2        NaN! P0 ~! f6 A8 R; N3 r6 `7 k
    3    本科二年级学生
    + r1 I7 g$ R7 Y4    本科二年级学生% @) X5 I9 }1 ^0 ?; b
    Name: Grade, dtype: category
    ' _) J0 }: z6 f$ E7 [Categories (1, object): ['本科二年级学生']. v) w- h2 t/ `/ }2 F) h- L
    18 g3 z2 z% x) J" P1 b1 i
    2
    * z+ @: f& `- o3$ a+ Y) o0 m+ Y1 o8 E8 U( j
    4
    : S: i; H& A% i0 U54 ^% X. F' V, f; x3 X8 t
    6+ q, m# O3 h/ U& A1 t0 {# X
    7
    ' `( u6 v, P6 Z7 m8. A8 y8 d8 S/ h$ d# M
    9% f7 e5 G* b* p* H. S5 k/ E
    10
    2 g. _" {4 H: T5 U  O9.2 有序分类
    $ r1 \5 P+ ~: `! r' ~9.2.1 序的建立
    ( t# ^" A" W: D  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
    , x' r; S) \" h9 a# x% M. Y% l! C- |% V) S' f8 k0 {
    s = df.Grade.astype('category')( b4 s& Z6 o8 H8 F8 u
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',, i) S0 Y: t( p- L
                                  'Junior', 'Senior'],ordered=True)
    & Z% i$ f2 w: p; m* ?' r% ?s.head()
    ( [4 y; L% J  j/ z1 G8 m3 ]8 Y  qOut[24]:
    # q! Q& M* y5 O; \/ e0     Freshman
    6 m- t& a/ O" W* V& n/ C# v  d3 ]; y1     Freshman  Z2 D% T/ u" L# I
    2       Senior. Y2 ]" n4 l, I6 a" H
    3    Sophomore
    ' f, Q1 d9 y6 n* {' J4    Sophomore
    ) n: [, _9 Q2 i  A* ^Name: Grade, dtype: category
    1 X$ w9 `! m* D, d6 B& }Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    6 G% s+ A  k. R6 i/ l% i5 e9 T( R3 n  `( O# S+ m6 v6 `
    s.cat.as_unordered().head()7 J  C3 f: Q2 G. }4 d; m
    Out[25]: , T# x5 M- K6 y! ^- W! p/ A. }
    0     Freshman8 ], r/ K8 V- m$ p, g% [# Q
    1     Freshman
    % w+ o1 t& m- R' B2 J/ j" `2       Senior
    5 c5 L' Y8 l, U% W) B3    Sophomore' D5 x& i8 t$ U: |
    4    Sophomore. [; ^- S5 D7 X2 U4 T, X( h
    Name: Grade, dtype: category0 N  u% f+ t/ X$ m. A* \0 \
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    6 s7 n4 N4 i+ x3 y
    , [, l5 f5 T# H1$ ^* B! o+ I8 D# N. V4 k
    2' o! P# I7 H5 t6 {7 Y, h/ `9 O. N
    32 C7 z" ?. k( w! J3 a7 M
    4: L4 @; g, i- g& A0 n* U
    5
    ; X9 E# X4 t# I  R. {/ W) U6
    5 `+ W( D2 e. A( I% I8 Z7, D* Q. T0 q" R- \* t/ W) J
    8
    4 `! }8 q0 B6 u$ I; I8 E# {8 h91 f1 R" y! g& A- s' _6 B; }: b" E) Z8 f5 C
    108 |% f9 X" X) j0 ~' \( [3 B
    11  C, _/ J& u& T* B, `* f; M/ w* u
    12
    4 i* q$ H# ~4 Z% D! q6 g. f13
    - _! ]# l  `- z* E' e3 x14
    # z+ L, o. Q) ]15
    , p% [) ~, A" n) a16
    ( G: y+ K9 @$ [1 D7 m. E17+ F$ X: R& \! s" s$ [+ d# e( }& R
    18/ R: B8 R& d( Y% C8 i( h
    199 ?$ `' ]* T3 B0 ]& E
    20
    4 z' d$ l) q% e# h6 h* B211 J7 b. {5 H4 [) u# B9 k- Y& g
    220 m" W3 \" P2 s: r& _  i3 n
      如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    4 D) A9 L- E6 D% N) N, U! G% U" P6 u' B6 u7 u
    9.2.2 排序和比较
    " [/ y- P$ m! G* ?& g. F4 D) s在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    8 o! K9 n5 L. Z# t# [6 s+ b; H; l
    3 N* A: A+ D  [) K  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    * ]( R2 {, V4 v# C1 n! G5 Y7 [( m6 R7 q' q/ i$ |' a
    df.Grade = df.Grade.astype('category')" p* G% C1 ^: w8 f7 [7 F6 R
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)* p" P3 p7 c- A2 ]
    df.sort_values('Grade').head() # 值排序
    6 Z6 p+ ^0 w7 a  E& _Out[28]: & B+ p1 C3 |) m; \7 r# e8 x# M
            Grade           Name  Gender  Height  Weight: x, r# W" q( L, @) [  k; ]& l; M
    0    Freshman   Gaopeng Yang  Female   158.9    46.0" e7 K. ^+ E2 k) Z# Y
    105  Freshman      Qiang Shi  Female   164.5    52.02 ]) o2 |+ w" L
    96   Freshman  Changmei Feng  Female   163.8    56.0
    / ~. a* N$ [' |* Q1 P! _+ j3 q9 u1 d88   Freshman   Xiaopeng Han  Female   164.1    53.0/ A4 ^! K, b$ d; g
    81   Freshman    Yanli Zhang  Female   165.1    52.0
    9 ^7 l" z9 n% L. K! v6 F1 c% F, w- M
    df.set_index('Grade').sort_index().head() # 索引排序
    / {" D& y, _( q- v$ Y; o9 O9 lOut[29]: 0 _! Q3 w$ G' k+ ~
                       Name  Gender  Height  Weight
    7 U+ R( t/ r3 b2 v; @1 dGrade                                          
    5 J! {, u9 v7 J# p- qFreshman   Gaopeng Yang  Female   158.9    46.0, K5 f3 S$ \4 `
    Freshman      Qiang Shi  Female   164.5    52.09 I2 C# Q, o% A2 L# X
    Freshman  Changmei Feng  Female   163.8    56.0
    2 Q; Y, p* |& Q' O. xFreshman   Xiaopeng Han  Female   164.1    53.04 E7 F  h" Y( m8 u+ j5 d+ }$ _5 E% W
    Freshman    Yanli Zhang  Female   165.1    52.0, \  G, @/ t, m0 P% p+ X

    , x8 S) m: t; z' O1
    4 u3 h$ H: G! M8 s) {6 e1 g2
    " O7 ~3 x- {# r7 {1 x& ~! X3
    ; m! N. O$ O5 U4 y6 x! H9 H+ q4, V7 m, U  N) u6 ?( S  `/ q
    5% y* I/ k/ T* o, i
    6) C, K; Y% z4 O( N: c2 P, m
    7
    1 O' @9 ]/ w2 P4 ^! W3 D8
    $ _. o0 J+ ]9 K. i9
    1 p% G5 I/ D8 ~3 u. F10
    6 o- Y6 P5 O( m11
    9 R/ L2 |7 I" }) V12% P. J* x5 ~) N( ?4 c+ D0 F; h
    13; a* \% U5 `: A& X: l$ O
    14- R  p% C$ }: k6 J/ |  w
    15
    ! F+ M7 i7 _. J" I2 h! h5 \16" v" s& S* t8 q4 z0 M
    17
    7 T6 Z& V& z3 S- f9 W& x& t& e18# k3 r, o# v* _6 Q  C' O+ Z! S
    197 z5 O$ j! f8 _/ z) A
    20
    2 W" W; }5 I* ^  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    9 Z! v. ~5 t) O1 l2 s9 x  R/ G; m1 \. [4 b
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较); m) S4 t9 w) ~% _5 \2 c
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。, E- r$ E8 |; \& I
    res1 = df.Grade == 'Sophomore'
    , |" [8 n" v# D' |
    8 _: w) B/ t: b. kres1.head()' e6 y5 W% R$ Y: S  T' Q
    Out[31]:
    0 c, `' V( k5 k- }( b! T0    False
      |- n, P1 q( p" y9 w! K1    False
    $ i0 S; F: c- A, c. H2    False, u+ E/ N% P2 ~' U+ R8 }
    3     True
    " e$ q9 @# c3 H% C. B/ W( r* x4     True
    3 E8 C' N7 }! f* @. k4 lName: Grade, dtype: bool6 `+ ^+ l2 D: v4 d

    ' R8 C9 u+ @0 ^/ J  d0 Bres2 = df.Grade == ['PhD']*df.shape[0]
    - I5 a  u4 H4 k: f: S6 e4 v' v  F% E5 E. ]& E
    res2.head()% M0 P2 s* n9 U7 B2 x' h" u
    Out[33]:
    8 V& F; M1 ~6 r, y+ u) m0    False) o, n9 W# t( B. N2 c# }
    1    False9 u; s: Q# Y, t
    2    False
    4 w. ^% I5 z  p+ Z" I3    False9 Q2 N/ {3 g3 |3 c7 S/ v
    4    False
    ) x, w* o1 e6 C. q  _/ IName: Grade, dtype: bool
    1 W: T/ x5 m! j4 z* q' C( t. O
    8 {1 j+ J1 j+ [res3 = df.Grade <= 'Sophomore'1 L) d, n6 q* V; d, k
    # C  _- J) g1 U6 Z
    res3.head()
    6 ?+ J% `+ n! h* ~$ ~+ s; POut[35]: # q$ n9 k, }( [
    0     True
    ( d" l( l: ?( y9 d1 p& |  Q# O+ n1     True' I1 ~: m# H9 c/ [$ b9 q
    2    False
    % N+ N3 Y' ?  S- ^7 Q% k  e3     True
    : q$ x9 R! X8 j# ^4     True
    # z1 ]! n5 ^6 GName: Grade, dtype: bool
    ; Y( v' }1 G8 k1 `4 {- c; R0 }: t$ r/ `$ W4 L3 r9 ?
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    8 b. r# i% c: t5 Y2 p% c7 ures4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    ( r  A; |2 ^" g, c
    8 O: _( ?' u8 J" O  yres4.head()
    6 k: M0 }. O1 D" q+ U  Z% N5 sOut[37]:
    1 j( z/ G, b) [+ y0     True) a1 z" l% Y3 x
    1     True  k1 r3 I6 A( @# {! ]
    2    False$ \; m, N7 w5 q" Z# m
    3     True
    ) x0 N  e+ O8 m2 p) o( N6 ^4     True3 A* b$ w- L, W! M( y" t8 G. N
    Name: Grade, dtype: bool
    * G6 _) d, L. s
    ! n* [$ k" X! `& t$ j* ?3 x4 J( K11 n4 [! S/ y9 ^8 j; k) K9 P* S
    27 m" o0 y; s" G8 g; V# E4 z, p
    3
    2 Z8 I' w$ g" i7 j44 o+ I( M8 f: {! ~2 g  t( J% S. N
    5
    : ?1 m8 V, k! T: |* A. k6- {3 `7 x" l; a( X
    7  u8 B. V  q2 w; a7 S1 w# |
    89 \) H$ a6 U/ J: H& ~1 Y' H
    94 [1 P- C/ Y" \7 U+ V. x1 x3 E' q
    10
    # D3 x2 D" [! W; i- n/ K11+ _) O. M& d& }: L2 z' P$ h
    12
    3 c- S7 s: ^, s* I0 ~% N13
    * a) f$ x" O  I( i  ~: }7 O2 |14
    6 {! J1 R  U" G5 p- ?; }5 Y6 M15' r: c1 N+ m( A
    16% @% ~% g; T# x# A: G
    17
    3 s1 M4 k$ b" v3 r4 ~18
    ( Z: ]4 @' C: ?# K. x: ^* ~3 \- {194 D8 F4 r2 N$ h( r1 e
    20
    0 F- t- O9 r2 H21" B6 s0 {  j* ^5 H/ y
    22
    1 Q0 n; v& G! q* y8 I5 j) h23
    6 j5 R& W9 p8 W# Y( a3 m% X24
    # u' i! @& B* X5 I! s- Y# c25
    . z) C" b/ t* c  Q# x26
    7 c9 o' V! d) e' h6 Q6 ^, W0 o  n27
    7 b" M& e/ x' ]0 c287 P: c% u+ n! k; V
    297 P4 ^! G2 D7 M& u3 t
    30
    ! s4 W9 B! ]0 p311 L4 b3 G* `8 w+ e
    32
    6 S0 q! W3 e2 Y, f333 ]; v1 [, Q* z0 }
    34% R( s0 X& h" o. s- ~
    35/ V1 m6 M  ]$ o1 ?) ^, A
    361 e* E2 `! B. N  }
    37
    ' i/ D& o! C  r# K% H' ~38
    3 h, I$ }, A: M- S" N39$ E2 _$ R, s) {. G: i/ ?$ W; x
    40" m$ j  A( W* b/ v- b8 M( G% U
    41& K8 |8 X! ]6 R* t2 ~8 C' K2 v
    421 k2 y0 v4 D' a" v
    43
    ! y7 D9 _- A0 j6 R  @2 k44
    1 r/ a& x5 [, d% `% ?, V9.3 区间类别- m/ x' R  _; l$ w" B# E
    9.3.1 利用cut和qcut进行区间构造6 h; V$ K3 o: f) |9 M
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    3 m9 e+ O# W! R9 R$ Q
    8 M: V: `. u3 s- P: Ncut函数常用参数有:2 @3 H! G) [. k3 m
    bins:最重要的参数。
    & W! |% b! g2 I& X, g% d如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    - T, e) \" r! u7 x$ l9 c, ]也可以传入列表,表示按指定区间分割点分割。
    + R3 K8 X9 D. Q! c# X  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
    0 @. t" U( p( J  w! ^" {8 f- S  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。1 x% E! u1 p# `4 b" r4 U! Y
    $ Z2 r! ~3 _( {, v; f  @
    s = pd.Series([1,2])
    4 p3 V: h6 p* h8 w5 r8 W# bin传入整数' p" I3 _+ b% L- ^
    * l" O7 u# Y( [, G
    pd.cut(s, bins=2)2 z  P) t$ D$ B1 K9 h' W8 v( x! I
    Out[39]:
    ; |# t( |  t) h$ i! l0 p. m* J* J1 I- \1 }0    (0.999, 1.5]4 f3 v! h& l( X4 T
    1      (1.5, 2.0]
    : R: b" A# A2 L# I, }, h5 ddtype: category
    0 f! h3 W' N* e. B$ i: e" u) |Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    / \* Q& ]) [" P% \, @9 d( k
    ! J9 h. X) j; F1 v; b4 v3 [1 M( ipd.cut(s, bins=2, right=False)
      i, b7 N! |3 c& }Out[40]: & M8 o# q8 o. ?- v6 T. [: H
    0      [1.0, 1.5)
    $ v% V( i5 F4 q7 w+ ?2 s1    [1.5, 2.001)2 P, G' O6 Z& ?3 u/ Q" w
    dtype: category5 G) n4 n, k8 B1 {
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]0 _4 [% w, [- }7 X9 M3 N7 X

    / A; D! W) m' `9 k  ?/ [" ^
    ) `0 f: g2 G' Q" r# bin传入分割点列表(使用`np.infty`可以表示无穷大):" n3 ]: B9 g& z
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])5 _* \4 @, o! d8 p! ]! r
    Out[41]:
    8 M; L' F1 r$ U: B' x5 `0    (-inf, 1.2]
    6 S4 d$ t; N; o/ w1     (1.8, 2.2]
    2 c0 s, }* s% ^dtype: category
    / i7 v1 z- b- y" ]Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
    - a3 n, i( `; u* v- c
    + p8 H  F/ q* o  y' d7 X! R# Z, E) _16 n) L1 |& d& @: W7 g6 @
    2. I& B( ~# f2 B) C: A! f; c
    3
    & M: X; K# E. ^: G# b: b2 j( {4) r& b  ]0 s2 X' I) u3 h# s
    5
    ; B6 _7 ^  R- l/ J0 H7 K3 m5 N6
    5 {; t( a7 G5 o9 d& U. K3 _  K8 r7- v& e. _$ r7 r% N6 Q& Q9 V! I
    8" Q0 p4 Z/ H+ c
    93 Q( A  o" P" H2 D
    102 E: R' r! y) k
    118 ~. Q; s# g7 F9 F
    12) R1 t; t% D. [. y# ?3 Z
    13
    + y' V4 ^8 N3 |0 V2 M! H14
    ! `* I8 [( B  G) x- }& _150 ~% E  l  U! z" `* D. q
    16
    8 x$ ~% M: [4 K6 |, d' T$ y) {179 Y  x+ w* x! X- I! o8 T1 e0 w
    18* Y7 }/ i4 d- H: `- Q  F
    19
    ) g* C5 u' P0 q20# G  g* Z! P2 z4 J
    21
    : W  j) E$ g" `8 L' @# ]/ t22
    9 Y8 ?9 ]% O6 v! b. z23( z, ^* P0 a+ B6 Q" d
    247 ~+ U0 ]* n/ r) `& S/ }* Q, P
    25
    . e/ ^' M: Z7 j0 @labels:区间的名字& R: s/ B2 ^9 U8 Q1 U3 g
    retbins:是否返回分割点(默认不返回), P  V+ X* p0 b2 c
    默认retbins=Flase时,返回每个元素所属区间的列表
    ) s6 `$ d( V4 ~/ q2 A9 [( ]retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值$ i  p9 o+ C3 z% _# y" e6 T2 O5 M6 z
    ! L% N/ I# L8 Q
    s = df.Weight
    ; V- b  x) L& B3 V$ |res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)$ J" v' u9 h& ^1 t  ~( ?
    res[0][:2]
    ) l7 w. i+ |1 a! |6 Z7 P" I: m7 \3 U/ y8 m- C: G0 j
    Out[44]:
    . p6 j) {% P2 v& _0    small- |/ ^' ~3 X4 K! R, s3 H7 S
    1      big
    1 ?, ?5 L: D& S  l3 [9 }3 q, x& ndtype: category
    ' j3 q9 x; A8 RCategories (2, object): ['small' < 'big']
    / b1 A2 D2 B0 s) b8 L  V0 `
    ' ^1 v  w" I1 ?) rres[1] # 该元素为返回的分割点. r# w# t, Q) Q& z1 o
    Out[45]: array([0.999, 1.5  , 2.   ])
    . L/ x  R0 O, T$ h( j  K1
    7 D) T  g8 E+ A- J& q0 j* u2
    0 }% z$ i8 [2 u- {. c) h/ H3
    8 g3 X/ ^& |7 t4
    9 R. }9 p: h: m$ v' u- I' Z5
    - d6 {! |1 H: L2 t% `( D69 a# U8 ^3 Q+ y$ n5 Y: u
    77 J/ T6 A' C9 w8 A4 a7 B' u5 h8 k
    8
    3 g% ?7 y  ]' b% U  n! `& |9
    0 B$ `1 K' B8 c* E! _" i7 R10/ x' k3 w- S" D
    11: G3 M, _; F8 q% K! D* @: J, T
    12
    1 C( M$ ]4 T  o3 \( uqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    4 h2 s4 D1 b; j' @q为整数n时,指按照n等分位数把数据分箱
    " b7 ^/ H' w- ?" j# E3 sq为浮点列表时,表示相应的分位数分割点。; |9 M/ Y% k! f9 j% a; I
    s = df.Weight) A7 A! K) Y/ K( a
    * V: D! }& K  ~- g. z
    pd.qcut(s, q=3).head()
    7 Z3 v8 t& n4 F( s& [$ Y, mOut[47]:
    7 o- ?5 ]. j/ [) D0 u0    (33.999, 48.0]1 O. ]; D+ b# L- c$ [
    1      (55.0, 89.0]6 \. ~) s) H$ ?1 @& O7 ^6 V
    2      (55.0, 89.0]
    0 p& A1 u8 {2 e1 B3    (33.999, 48.0]
    % s9 W8 a& I! n4      (55.0, 89.0]
    , w, a, `9 [. B- @, d6 {1 mName: Weight, dtype: category
    * L0 F: D3 Q/ v6 n+ [2 B6 TCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]4 m# o; G# ?8 s, z1 x8 n! P
    " Y, S0 ~* i6 t$ k
    pd.qcut(s, q=[0,0.2,0.8,1]).head()" v& t7 A4 q& i2 B
    Out[48]: . k: N4 U$ t# [1 i' y7 c' F
    0      (44.0, 69.4]
    ! `3 s, p1 f5 s9 n) O1      (69.4, 89.0]8 Q# s- E$ C; T
    2      (69.4, 89.0]
    8 h1 o' k& m) |  n" H( [3    (33.999, 44.0]; e/ Z2 L% a- K/ J7 w# [) u. x' U
    4      (69.4, 89.0]+ q9 Q  M# K& x- W' Y9 Q( m/ p
    Name: Weight, dtype: category* D  P, [2 l. {+ F* p0 V* m. a
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]5 Z4 ]8 ]9 b; s
    8 w" {7 m4 R- T+ Q" |, A* G
    1
    9 X+ j( w4 J2 O: p" {& ]4 s2
    6 `2 k! w0 Q$ y3
    1 \1 N/ \+ V5 Y4 ^- i0 T9 N42 w8 c. S7 R* |! k- r# Z
    5
    - T+ B! Y% L; E3 M& g6
    7 ^. q/ Z/ F9 y* h9 @  a7
    8 \7 ?6 ^# k- O# i3 {& ?: p5 ~80 j# x, L# L) i( C
    94 o" p. \% o% E+ D! ?
    10, U7 d: q* R3 ?) p  a
    11
    4 {9 d& s2 a, r: @125 J6 S; Z: v. h1 a2 g
    13
    , \5 K. t( U8 Q: }) |: t14* r; R. k% ]' o# j/ f5 g# `/ C
    154 G% X0 m( d9 c$ }& M1 }. K# T
    166 m5 A- q: t- N- k1 u3 D
    17: O! ^; e- e% y
    18
      r- H( f( N7 m# G" x19' N+ k9 ^# A' Q5 n! B" Z! ~2 R
    20
    , m, T# ]$ D7 ~21+ t7 \4 x- c. S# K3 l( X9 q
    9.3.2 一般区间的构造
      _' S7 u0 V/ v8 H" t0 j  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。3 |* ^, [( w$ E: U6 R

    + B* R7 {# `) q; S7 b0 c9 i4 I开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    ; L5 m. s; T1 Kmy_interval = pd.Interval(0, 1, 'right')
    3 n  g3 _4 s3 Y
    - q8 U8 A. v; N# Kmy_interval* h6 X1 M2 i# o
    Out[50]: Interval(0, 1, closed='right')! S1 ]1 U7 ?/ J- @
    1
      \, z; ~% X) [/ }: X- {- j2
    7 v/ K, I0 J/ a8 @7 U& N, G31 n) ]  Y% S6 S& h3 U
    4
      H8 ^7 K9 p: p: d) t区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    0 ]1 o7 ~3 Z& i2 _4 _使用in可以判断元素是否属于区间
    7 J3 t9 c/ _1 T用overlaps可以判断两个区间是否有交集:
    4 W3 Q( l. x- Y* y4 w7 e; e0.5 in my_interval
    ' e6 v4 i! X, E6 I/ L- m
    # Q. o+ Q) I  Z& T% K: m5 BTrue
    + L, T# J6 q: D% m3 V0 T( G18 L! B, ^2 P) k( s  |
    2
    . Y. ^2 h6 @4 f3+ h/ I" z$ N( ?
    my_interval_2 = pd.Interval(0.5, 1.5, 'left')- W* \1 d5 `- A, ~* @
    my_interval.overlaps(my_interval_2)
    ; g% P! r1 O0 ]) B( o/ D4 p! w
    # o7 o$ e% ~' X, I& s0 L8 N! |True8 G3 F/ \) ]6 o$ U+ z
    1
    8 L. ^5 F0 u0 w" l% i4 a2; z- r6 W8 i! m0 i; E
    35 }$ @, u! R) }$ f+ ^( H- `% R
    4
    9 w- E  k. J- g" Z/ n1 K  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
    " n& q, g0 E$ F9 ?! p5 U3 `# R3 D& `3 S5 H: R
    from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    " B% y( ]+ b9 t: A  R. _# |pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    & N3 E* F3 P) h' X  [1 f5 e  j5 S% u0 ?) K' i! v
    IntervalIndex([[1, 3], [3, 6], [6, 10]],: a; K: p% N/ M
                   closed='both',
    % j$ x$ Z% e$ m7 l* h* n               dtype='interval[int64]')
      X' J9 j* u9 k! i9 g& I18 j% w7 c8 _$ Z3 g4 Y
    2
    ( j( j6 V  ^0 Z- k4 o9 W* l34 G+ o2 E4 h% f3 S# Z
    4
    ! @/ i# [* h0 C" p) c% G5
    : p: q1 f) Y! J: y9 Y6 K1 z' Hfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    8 ?: o2 G# P1 H" t/ x( ?. {pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    # T( b+ A' m) x, @' f% ]# W' V, l
    2 Y6 u$ z8 S5 W5 WIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    ; y% x+ _8 c8 F: @  d$ c! ]$ L                  closed='neither',
    ( ~5 o% N/ J' d/ B, j. H                  dtype='interval[int64]')
    7 B9 l  w7 l+ Q/ o6 {1! \2 Y5 b* P7 W! |$ @' ?9 {3 W5 I
    2
    9 T9 d6 G  M' [% t- M1 e* T* k3
    % m* A1 E7 `5 w& K. N9 ?; M# E4
    & H# z; g: Z- O, o5. n& j' |3 _9 K; u% O+ `: E
    from_tuples:传入起点和终点元组构成的列表:3 Z* |! U  @/ x3 _7 T$ p
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
      s" X6 B6 e, J8 [% l2 O/ ^& ?. |0 y, q
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],/ f3 R. C- ]; \+ A! |. `) u, x
                  closed='neither',. f4 z/ m$ Q! @( T9 n9 t
                  dtype='interval[int64]')) B0 ^( L: W! c
    1
    9 U' |& K( i7 T0 g3 @3 g9 S2* R5 p) Z1 F0 i6 H  p% E% \' r, e
    30 k6 X) o+ {2 N7 V+ T5 N: u6 R2 x/ `
    4
    4 ^! s, Z5 B: p' \* j1 |5
    6 F( Z% [1 B7 minterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:/ x' ^5 i9 }: G& y
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数) v1 u1 f) z9 f' s7 d: H& s5 d
    Out[57]:
    # D" j$ l0 L2 m# q5 q* sIntervalIndex([(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]],
    % l5 p8 b3 L/ I; p/ S              closed='right',
    * [# b& a+ ^0 o2 u/ o; k% @7 X              dtype='interval[float64]')7 ?9 {+ K. Q+ f/ L2 y( g) p

    . N0 v& G, ^0 N9 X, d) spd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
    ' J4 e* z6 h0 bOut[58]: ; |! M) M1 ^  r. Z! W* e! l
    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]],
    4 ^" W& h# U5 l" G+ y. c: |              closed='right',
    - s  B+ l/ ~  d2 w* @              dtype='interval[float64]')
    8 s4 Y% e, J1 b" P. c7 R" {7 T18 B: w; w5 q& u* M: ], d
    2
    : ]1 n( O! w3 `5 _& B  h& R3
    # d0 \$ A0 V3 o0 x& l5 Z9 g- M4 x* L4" i6 O3 n! u5 q6 m# h
    5
    ( z) N" z, c) q1 W; S, G1 K6
    # t( Z9 h) v" }7
      ~3 Y, w9 y  D& e9 E8
    9 c7 u+ R* A, D6 v9. j1 D" r3 u8 e, m4 c
    100 P# U! o  O  z5 L1 [$ Z) n
    119 E- R# P* {+ c: l
    【练一练】
      t9 _9 p0 S% @1 g- X  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
    * ?& N, H+ p. P% K0 O5 S* b5 l; ~3 c3 V5 K9 F
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。8 \; ^" v. t2 H6 k# F2 V+ J8 w

    2 ]! i, k3 I) w5 ]; e- kmy_interval0 i* X! ~/ ^$ Y% v; i1 g# }
    Out[59]: Interval(0, 1, closed='right')
    ; t. O' i. y: X5 G# O0 v+ ~' X) P6 w4 k3 M
    my_interval_2
    3 R& g& Y; e: d- E3 EOut[60]: Interval(0.5, 1.5, closed='left')! }+ |% K, b: f
    " f% p! z3 r+ u3 H) q6 l* O$ i
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')
    ( A0 G3 e$ K( @& J* }Out[61]:
    3 Y% T( N8 U# ^1 @; qIntervalIndex([[0.0, 1.0), [0.5, 1.5)],9 z, \* }4 Q. X& f7 T) y
                  closed='left',
    % x: `3 }' N0 \4 Q+ i2 ]              dtype='interval[float64]')
    : N9 I4 A( J) @% ]" |& p3 K7 W1* e9 U1 W- D$ e: `+ o/ k$ I
    2; i6 s* ?! w* Q
    3
    . S& C6 j8 V2 J, ?# o$ \; t6 E/ p4
    - O% t2 j, C) X) L, X7 a5' k8 c" ?% O, n: y( G8 J, ~
    6
    8 ?4 a0 z5 |# e- P; N1 V1 R1 m+ ]; x7
    5 v' p% V$ {! G) v8
    9 R! `' |. m' @- z9
    7 x( y2 @8 w! G* y, ~4 R109 a' d! n6 v! h5 W% H( x
    11
    * q8 i+ h" _5 U9 f9.3.3 区间的属性与方法8 p+ S, k; r$ O* Y2 K+ R8 c
      IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:4 I! A9 E+ O5 z) v
    % Y6 Q- y  j# H0 L2 H" n
    s=df.Weight* k5 M! }" s, C! R& p+ f) t. G
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    ; l" [' A: I  {& a. q0 a3 M4 U( Lid_interval[:3]3 v: {/ G( M; v5 I) E$ e

    4 t# F3 U7 G4 P1 e; rIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],0 r0 }5 Q% d5 L+ p
                     closed='right',
    & K! X9 E$ c* d8 Q& |. |! i" d  {                 name='Weight',
    ' a; f8 z: T! a5 a                 dtype='interval[float64]')) b3 B6 c+ u6 k' F) D! A
    1) f1 j$ i8 v# y+ g1 m& Q
    24 Y2 z' @3 n1 n$ \2 o; k
    3
    ) \" G( R8 z2 g: b& ~4
    7 w# }# }/ ]' h% p& P" q* j( ?5 e5  z) m! l6 g, Y- b  y5 X4 A4 D
    61 S3 t5 R! B& G8 S# U1 f
    7+ b4 d; l1 M1 H& M
    8. i0 H9 S4 l9 E# P6 H% ?
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    1 `3 |! i$ a0 j$ P$ Qid_demo = id_interval[:5] # 选出前5个展示! J( j* o. Y- R

    7 B3 l  G5 ?5 b' t- _: Iid_demo
    ; ~' x! r( ?: ^2 @Out[64]: ! z& h: T9 C' \2 P& H* \' i+ d
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    # u. ^! I: W& w1 e  S4 ^% H              closed='right',
    5 V6 ?3 F) O$ \, E# ~0 u- J6 L              name='Weight',
    1 O+ C! p* E% p$ l$ `" l( F, F$ v8 n8 @              dtype='interval[float64]')
    # x) v7 |, b. U8 r- P+ p
    9 p( L" k* H3 }# M+ nid_demo.left # 获取这五个区间的左端点* u- r# K( q! @# c% B
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')& m6 W. J& B3 ?  ], \5 L% K

    ; _8 ~# h6 {4 i0 kid_demo.right # 获取这五个区间的右端点
    $ g( a: h4 a) z3 E/ }& K& h+ @1 @8 aOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')+ ~- W6 C( l* X9 J

    . U3 D  p1 q3 O4 Q% j4 qid_demo.mid# A; R, u- P# _) H
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    % u8 m8 p, t( B! q$ p6 r* {8 L; _& T) ?1 q4 u, e
    id_demo.length
    4 F" Y, B+ U7 |6 ~5 m- |3 `Out[68]:
    % l1 ?7 E1 N; @# X' dFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
    8 \: C% D; q+ t0 k, t1 L              18.387999999999998, 18.333],
    9 z  R& v  Y* D8 \$ S0 t/ T; Q             dtype='float64')- G3 R, M6 L% b  V
    ) j& V  Q5 D8 o. _
    1$ S, k- g7 B  I$ ^
    2
    5 l- n, P* f3 \- O) ~8 k3
    ( b- H% d( P3 G6 F5 R8 h1 U/ ?4- B& ?  X# z# ~! P' }8 W5 \
    59 M% [) ^5 |/ p  u' R, ~
    61 E8 b2 K3 q2 |! ?0 L0 j
    7! W! ^0 j3 M. |
    8
    ( ^/ y& f, X8 s, J- \97 p% }( g+ E$ h2 L9 H! ~7 d3 Z
    10
    8 |; _/ l1 @2 s" j# {( x/ @: y11
    + Q1 @4 o( ^# h4 r" t  C, O% g% g  _121 m( P  w6 ^! ]8 y* d
    13
    / m* R7 y. O) J9 c) W6 O5 T; r/ g14
    , I# M, o. z( W15% e" {$ U1 K# Q
    16
    . `# f% g* Z, P1 ~$ D17
    + n  i& X  i$ s$ g( A; a18
    - C( Z& j2 y" r4 @7 V: c- I19
    4 E- W4 k% B, y( ?: e# J20
    # }0 h6 j; l6 h* ^" N+ ~6 i' \7 ]21
      u# J1 |' I. z, g- [* N22" ]( l1 t9 Z& W! N4 I" t: ]* C6 `, r
    23
      s& I/ N' y) O5 q+ bIntervalIndex还有两个常用方法:
    ( D6 v3 J  c! d' ?7 y" ncontains:逐个判断每个区间是否包含某元素9 n/ q, l6 o" X# T% q' P  y
    overlaps:是否和一个pd.Interval对象有交集。
      C3 L# i; Z4 ?3 g9 Q# ~& ]id_demo.contains(50)
    2 h2 x$ _: O$ m2 s2 D4 h, m; z! G( f) }Out[69]: array([ True, False, False,  True, False])+ H; f7 \: o4 c; [, c, M' c

      Q3 U; y6 z& p7 C. jid_demo.overlaps(pd.Interval(40,60))9 P! z* u8 s/ t4 s% ~
    Out[70]: array([ True,  True, False,  True, False])( B4 F' F& ^& ]$ _) v8 @
    1
    % `+ V7 @% r, n% ]% z2$ `5 D! U, `7 [
    3
    + Q# t5 ]; Y, C# W6 W& ?. I- w  v49 ^4 H! V/ P9 `5 G2 Q3 l2 ]
    5# {# V2 h7 {) W2 H5 n3 |
    9.4 练习$ D% g7 \  O1 |8 a$ J' u  r# ]
    Ex1: 统计未出现的类别
    ( v  T  z6 @- W  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    ( O' [, x9 i& L1 c1 X. F$ l7 q5 `( I( o- ?# d% r
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
    ) k* Y$ z! b; v3 |" h6 Fpd.crosstab(df.A, df.B)
    # q8 r4 W* H2 l7 _5 I7 h, L% }% H, t2 E. ?9 Y  Z" D7 b+ K! `, j
    Out[72]:
    5 u7 u# U4 _) a- XB  cat  dog- T5 S% ?2 D/ W% U# m
    A         
    7 p5 n2 m# f$ c/ y  `' s# j; Aa    2    0
    . d6 ]/ J1 ^% {( yb    1    08 P6 ?- x$ K$ K) a" {, y. p
    c    0    1/ P% g) c9 i+ L9 H# |* G
    1
    . i; C& S/ M8 V. u7 R2& @* `3 w/ @0 [1 x2 B7 `' q% c- C
    30 v  E7 v7 _# y2 F$ ~3 u4 R
    43 ~( a5 J" i3 ^2 p, X$ u# H
    5
    & z' ]. N2 L2 t$ X2 H6
      J' C4 R- V. V. s0 M; U7
    - @* q) H( b- m& E# @% S8
    . E( P, F0 S0 C6 _/ b9 g- n94 G( f7 y6 F$ |- f( @, W
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:4 v1 _3 K1 I& h' c- b
    # L+ V' M, {" k4 k" x
    df.B = df.B.astype('category').cat.add_categories('sheep')8 ^" ~( n8 T* s3 s' K& z1 L
    pd.crosstab(df.A, df.B, dropna=False)1 q2 ]3 l" d- G% W

    ) K1 N6 @6 V+ B# e/ uOut[74]: " o1 K9 d1 w* B; t3 B7 @
    B  cat  dog  sheep4 W. s& C8 Q/ W5 G4 j8 E3 d  P' ^( ?+ x  v
    A                 
    ' D5 P% Q( {3 o" t; za    2    0      0
    4 K0 [0 a; ~' q, C$ J& Pb    1    0      0# ^3 O5 x& B3 W- R& g
    c    0    1      04 Z7 v! E3 r3 k( D; d" h, A
    1
    5 b; A( f% ]+ I5 W0 y7 }24 B& @& R+ l3 f
    3; G  ]2 S0 R' v$ ?0 F* Q
    4! c8 D5 d( V3 N1 K9 c* e2 h
    51 Y# u( a- l! f/ g6 }0 n) b9 m* ]
    6! R" f7 j6 i3 F* b) O5 U3 X
    7& o8 i3 S% G* g5 D3 p
    8$ e& j1 K; l% \2 d4 U2 y* @0 o
    9+ G9 i. s* F- q, i
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。1 [% M0 I6 r8 u) o1 O
      J6 T6 e/ m' C+ ]
    Ex2: 钻石数据集
    & m6 y4 j$ D3 {7 |; h9 `  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:' v' g, H+ [, q6 r% r

    9 n- Z. E6 Q7 g% n0 n0 b  Ldf = pd.read_csv('../data/diamonds.csv')
    , B3 D" I6 ^& t4 Adf.head(3)
    9 y2 p2 M: S) x9 p9 E3 B
    # s% V4 }9 D# E' X4 _Out[76]: * a1 h2 U; o9 V: T5 y& J
       carat      cut    clarity  price
    * M) p9 g# |7 F4 o, c0   0.23     Ideal     SI2     326
    . X. Z0 b$ q! d, m1   0.21    Premium    SI1     326- x( \6 S9 [3 _0 g6 B
    2   0.23     Good      VS1     327+ p: w/ V3 t& q5 U1 ^1 m; v7 L
    1
    ( a$ a% g' _- B. K3 U" F+ P2
    9 H1 ]5 d# L: Z2 |& \' T8 h3
    % [% V8 |8 e( u! T* l4 @& P! l46 d: R1 r& H: g3 h# S4 A9 f* Z3 }
    5
    5 u- r$ f% z- u( g2 k3 ~7 f- v6
    3 |7 a0 b8 |0 c/ z& `5 Y9 O% o  ?7
    2 L0 O  `8 n+ u$ ?$ k1 C7 K  K8
    3 f5 n7 _1 ~, y$ e分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    * s4 J1 z! \- ?2 z' C# ]% f钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    3 J6 `  M( {& n分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    , f8 ?, k) c! T7 E$ t9 ^- o0 \4 u对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    , `: t5 p( T0 b+ S第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    ( `& R) T/ ~( O' S0 A5 n对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    $ M  g, P0 A( S5 n; D4 P先看看数据结构:- z( G! m* Q7 b- }7 ?) f

    - R% \1 u/ |  m9 L8 Idf.info()
    ' j( l9 w/ K, I# S7 A( XData columns (total 4 columns):3 u5 H  @9 J, O. Q3 d' M* I
    #   Column   Non-Null Count  Dtype    C" J9 u# H; P6 q
    ---  ------   --------------  -----  
    $ c3 h8 c7 p0 ~$ e+ r- m3 | 0   carat    53940 non-null  float64' V* _0 {) f  I2 y% {; l% W
    1   cut      53940 non-null  object / C8 O( M8 N# h1 h. s) q' K
    2   clarity  53940 non-null  object
    8 o+ A8 c% k: h0 H5 R 3   price    53940 non-null  int64  
    ' m8 b" U* O* g' tdtypes: float64(1), int64(1), object(2)
    % q0 ~2 ?( ^7 ~$ t) W7 ]1
    3 u; t4 E0 V' P" Q9 B) s, U2
    4 K! y) w3 Q0 `2 O3& I3 s1 I+ q% m4 [! y
    4
    + j0 c( c: H  p, k0 o2 r8 y5
    / @& l7 ^9 S& l" b6& F9 w* f9 F" Q" t( T
    78 Z# q3 h3 B9 B7 ^" L2 o# y
    8, D9 Q" E4 z" w+ O. e9 T$ |
    9( _5 o) \5 G1 `$ e. A
    比较两种操作的性能
    ) b; o* ^# p8 _5 O, }) t/ Z" \- s  Y%time df.cut.unique()
    ( ~' X; l7 }7 M
    # S9 O3 S( O4 gWall time: 5.98 ms
    $ }3 s/ v6 L+ E1 I/ |/ M5 A5 p6 Y, Warray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)! h+ ]! h8 j4 |% W. \' m! F1 y
    1, P' k# {0 }! I* L* P$ F- S: ?
    2
    ! z, }; a+ |8 e6 r. [0 `) ^  E3
    , z$ L" _; P! T% D2 n. u2 b- @, F4
    ; [/ ?2 z/ E* `9 l: o  a%time df.cut.astype('category').unique()
    9 R/ h8 n6 A7 Z: E  [1 }+ I1 H- K; k, Z" p
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    , U- m& e  H% r: L- |; x1 e+ h1 u& i['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    * T9 l4 ?) a- A( U! `4 ^% RCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']/ O9 ?6 o" ^6 b7 \4 K) d; e
    1
    $ b5 U# v& k/ |1 [0 s% c27 a2 s- c* a+ D! I% K
    3
    / Y7 I. s! x* ?! ^+ x0 P) R4  s3 ^9 L) [8 @9 z
    5$ Y. s  R/ g+ Q
    df.cut=df.cut.astype('category')2 s& Y2 d: H; V
    %time df.cut.unique() # 类别属性统计,2ms! U' z: Q( [4 p: Z2 v( V% C; {/ w

    : F6 }: A% C& W4 GWall time: 2 ms1 J. W/ b! G" g% T
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']  N9 V1 ?4 E3 Z& p
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']6 Z: v& K$ _% k: Y. @
    1
    - j3 {8 l* U- w7 N$ q3 E27 E% d6 ]5 N4 X2 P' d& L* `/ u  n
    3
    2 X- d/ R3 |! o. s, }4( S; N5 A/ d1 ^1 ]% M$ X. S9 }
    5' a! A/ ~8 ?+ H! Q9 _
    6
    ( u) z7 j( y; B2 f: V& D& f# n对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。8 [# P- c. S4 Y* {* ^8 f
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']6 v( t7 L8 H5 m/ i5 V
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    ( {7 v# u: `; `: a, G: \! Xdf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换- _' x9 r% y  r+ n
    df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    3 h" H0 e) F. K9 ^' w. b
    - e$ I. V( M  j* W# L: m3 jdf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)0 U7 P1 y+ I# h0 n/ b0 V9 w% B
    $ q8 f% H4 |* f% |3 V
            carat         cut        clarity        price& E& l$ g& O1 s9 d3 l3 W
    315        0.96        Ideal          I1        2801) C/ V; W( l# C! M2 }
    535        0.96        Ideal          I1        2826
    " \4 q5 @6 I9 W4 j$ r/ R551        0.97        Ideal          I1        28305 C" [4 {$ X3 n& {* q
    14 E. `, _+ o  o* J1 U
    2& a2 @; J  U! r1 u' C  y, `
    38 p$ l- Z! i* l7 E! N
    4
    3 ?! k1 X: |" j9 d- Z) E. l- N- m' b" `8 R5# Q4 y2 f) @7 Q) q* \  A9 W
    6( d* @. e8 G4 E9 Y9 X$ N4 Y" D, q
    7
    9 x7 r8 A) x  R- L0 f; \81 R4 c/ e/ D6 N/ h7 s/ M
    9+ S) ~: |( C8 l8 F  \# Y
    10. u/ L  h0 j' Z+ z
    11
    6 T4 }! c0 X" c* T分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。/ G0 x6 c& W/ j: t
    # 第一种是将类别重命名为整数, a9 j; c( m# Q  |) w/ C6 W0 [2 T
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    6 g7 B; j% w( W* ]8 L$ m/ Ldict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    " p% i. ~1 q! C4 `: h* [  k6 C; T2 D. n( l7 m6 ~
    df.cut=df.cut.cat.rename_categories(dict1)
    ) a& T5 y0 g$ A% x9 O$ z, Hdf.clarity=df.clarity.cat.rename_categories(dict2)
    ( P0 M( U$ I$ O$ }# ^: b5 b/ G( Wdf.head(3)& L# T, m  H% C7 ?

    : M4 |1 ~! i& Z- R8 p        carat        cut        clarity        price
    ' E! g, @: E* g3 e2 R# U0        0.23        0          6                326
    , Z4 V' u2 i6 p0 |$ W2 |% [) p1        0.21        1          5                326$ X: i. j) v! i+ S/ _1 ^
    2        0.23        3          3                3273 D2 I% H, `2 [* d7 ?5 ?' m
    1
    " D) M* E8 r5 C- [2
    ! b& z. H2 \9 R! @' s, y8 a36 g$ @/ g* f- _2 k
    4
    & L9 P8 Z. f% }; `/ b5 F5
    % E: N4 T5 k. U1 z6
    . p# n) I# W5 B7 a* t  O9 _7
    0 ~0 h* R& `2 e4 d4 y* E0 S5 b- I8
    8 f( q, @4 o4 d3 U7 K  ]  d97 b* ], e2 f5 D) q  y0 x' N% j0 i
    10
    , L# G1 Z  }% p* N) z$ W; }0 G11# D* j' [2 Y; B7 a+ T5 [
    129 R/ J0 g4 S. |8 D
    # 第二种应该是报错object属性,然后直接进行替换
    8 P5 S6 v0 w1 |% Kdf = pd.read_csv('data/diamonds.csv')# G% Z1 [* d7 C% U- g# _
    for i,j in enumerate(ls_cut[::-1]):( X5 s/ q& o) Z
        df.loc[df.cut==j,'cut']=i ) l8 d2 o6 }& n* O* J: l* b/ e
    , }& b9 S: a/ @7 F$ ]" H9 X  N
    for k,l in enumerate(ls_clarity[::-1]):
    7 N; m4 Y8 b0 f( A; X. A    df.loc[df.clarity==l,'clarity']=k
    9 l' W" k9 ?* E- N/ e- bdf.head(3)
    1 i" M; `. y* a- ~8 b/ C& A( R9 K$ t0 R9 N
            carat        cut        clarity        price
    / Z2 @, o9 j2 ~4 I7 F% B% Y0        0.23        0          6                326, k9 A# \. U" g) T/ {" Y9 K1 d/ D
    1        0.21        1          5                326
    / J8 w( ]5 ^2 r0 T2        0.23        3          3                327
    ; u  `/ ]- E3 |. ^! B1
    1 a9 V5 Z; U; _# D% W& n+ ^2
    ( \4 O2 h+ {3 i3. C5 |1 }! M- x: ~6 @
    4" w9 q& O# @7 O9 R! Y0 k) d
    5
    9 P, \2 ^9 w8 h0 D64 g* B, @7 I& m3 t
    7
    & K* B: S( \6 k+ D0 ^4 M/ B8
      P0 i* f* w/ C' y7 D; q9. f2 E6 v& E+ |6 Q
    10) N; I8 `$ O; k6 j" m0 x
    11
    5 x+ \8 J- Q: k; I# A122 A- e; K# K+ _9 E( |! ~
    13/ M" v0 P! P' }! \9 T" b
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。2 O. b& I! W% M! {
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点6 C3 [' L! \5 _' ~) T
    avg=df.price/df.carat
    & v5 b. ?( y9 |4 R( m1 ~
    6 Q' J8 ?6 A, a* j; V+ N0 z- ddf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    1 X( Y7 ~/ d* e) u" ?                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    2 G) V% f( M( \6 g
    ( \) I2 z; E. @7 h. K3 qdf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    % C% n, z7 c0 x( G8 i' z                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    . ^  u* t- P! Z; T! O: |* Cdf.head()
    * y" h8 U1 Q4 o
    ' Q; c( L0 i1 G) w* L2 X. _" w        carat        cut         clarity        price        price_quantile        price_list  Q6 n; [" [) {( x; c" Z: [
    0        0.23        0                6                326                        Very Low                Low* E: p. |+ A, i: G& W+ n8 I7 Q; f
    1        0.21        1                5                326                        Very Low                Low( F8 N7 y$ @, {% t: u
    2        0.23        3                3                327                        Very Low                Low' @7 Q; _  b" W/ L/ G9 c
    3        0.29        1                4                334                        Very Low                Low
    8 i$ h$ w, M0 q1 E4        0.31        3                6                335                        Very Low                Low                                       7 c- l9 T* j- P$ H

    & U- U# u! F6 l2 S1
    * g( K! h. L3 @1 C) Q( i9 ^2( T: D8 y# J9 t% A% E5 B
    34 H+ p8 {$ I: y) M! L
    4
    " ]8 A3 t- D3 |- h* g58 r! {  T+ b: P% j9 j9 j
    69 [( |; p- ?7 S6 L" N+ T
    73 G) [" e# n& |) I( r' [9 x. S
    8
    + t2 O% z; G4 @  V7 x9
    & m! x: Z  ^+ i5 X6 t$ S1 E104 G2 D2 k4 `2 g1 |6 y- z5 Y) C9 _+ Y
    11
    " W, H% p2 p9 z3 R126 g% ]6 }8 @8 U6 s+ u/ F9 F
    13
    3 a0 B  F6 I7 J% [. }, ^0 `14
    . }2 a4 o  }1 C# s1 x/ x; ?. P15
    + @4 w  [: C/ b6 Z! I9 \! Q16* W. O" a( @, q
    分割点分别是:- q" D! _# Y" N) I# _: j

    6 ?8 c' [; G! p" marray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])9 `' x; U8 Z% ~9 R+ w0 w
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])6 H8 Y1 V3 C0 X8 f- k
    1. ?5 Z; {" q0 K* W
    2
    % n  _7 {/ r* R4 T1 r, X$ M) a/ ]第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    - y  L! f7 D# jdf['price_list'].cat.categories # 原先设定的类别数& _. C) R% i3 r
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')* O+ m& r( ]3 n' I
    : ~1 v7 M+ E1 _: @
    df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别$ N0 z2 [3 h3 E. b3 _
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    ! y) k! Q6 Q6 q& j  g- r' f8 c1
    . l( r5 H/ X5 F/ d2: i* [' [" o3 P' N$ z3 A
    3
    ; g8 [6 z# X9 O+ O& B' a  a4' {  \7 f5 F1 m0 Q  ^6 Z
    52 V0 T, ~  t2 M0 N; p" K- s$ P
    avg.sort_values() # 可见首尾区间确实是没有的
    0 G" J# _( n7 o7 v8 Z31962     1051.162791; c  g6 R* [% N- c
    15        1078.125000
    0 h6 ]) C: F# ?4         1080.645161. S9 F' a8 N) g+ R
    28285     1109.090909. l, W: A% z) h& {1 M) F
    13        1109.6774194 w( X1 W& k* u1 Z& ^4 M* h
                 ...     ; ]  L2 J3 u1 O* l7 K8 a
    26998    16764.705882
    9 d# [3 t- h- _. \- i' b27457    16928.971963
    3 k8 x& K% T. G: ]27226    17077.669903
    7 }2 I. F8 S5 g: P- @) S+ ?4 _8 S27530    17083.177570# N) y9 B: ^4 G6 J/ O
    27635    17828.846154
    0 l$ Z- s: Q  B/ R+ }. Y: s5 t1
    0 c/ v; O* ^4 d5 M2
    % }! Q. i* B* K# e2 D5 P0 w3
    6 z& \2 b. R1 x; n45 j& g) H( }/ W' A( W% z2 v
    5
    ; l  D( I/ Y7 R+ }% X( n5 A  n+ l. }6& t+ J+ M& c( w' ]6 u5 R: q) R+ q
    7
    4 |: n/ P$ d0 F# C7 ?" f8( v+ s) A  m" j0 ^+ [$ a
    9$ k, x/ I9 y6 d) S) K- w
    107 L( H$ J9 y) ?& E" Q6 r+ {3 e* e
    11
    ( a0 g6 v* s1 _" Z! Y6 {12
    & _+ Y/ X, p5 Y1 p6 `; a  S8 \对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    ( E4 k* p) p( q3 H: A# 分割时区间不能有命名,否则字符串传入错误。
    % R" H" Q7 t0 B+ B! X# y: M8 Eid_interval=pd.IntervalIndex(( e6 f9 C7 ]4 ^0 {2 K3 V3 |
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
    : d4 B) o5 n! H7 s                            )
    + m5 P! h, `0 a* qid_interval.left
    : e3 x; M; C, I" I5 Wid_interval.right9 n$ H7 J7 q6 f3 ~
    id_interval.length                           
    ( e( j! a2 z: Z0 }0 C- ~1
    5 G# K9 g/ Q0 [: f( a2 [5 D% {! I2 q2& o6 p1 ]2 F0 L. z8 T( l
    3
    & [( k; e$ U, K9 X. O( f4
    ( U7 j( [: E* K9 F" i57 ]- d1 O5 Q( G9 }/ X0 j5 Y
    6+ m3 t) h+ E3 F  c4 n7 d
    7
    ( t  P, ^+ v  e  A# ~第十章 时序数据0 h1 h" D: n- P. a$ _# j
    import numpy as np
    # l8 h5 c5 t) Dimport pandas as pd
    6 v6 f- t* S1 G  Q/ \9 s, M- A1
    , g$ Y9 }% Z- T( z1 a9 V- S2$ G% x0 @( r% T) f4 y/ ~3 N

    3 z) T6 Q1 }8 @& t, K; i7 U: q# P4 W* P( t- S: t
    10.1 时序中的基本对象
    8 J: Q! |8 T( Z# y4 h0 `  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?: g6 F8 l  c4 w

    + j1 X3 C4 d6 u% t7 U会出现时间戳(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的简写。
    * B/ M) d4 }) _2 [3 Q% z% \
    6 X# [2 T9 t- Q7 F. j' y/ \会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    4 K- B0 l3 c, z
    ) n! |9 }5 L/ E/ U8 D9 ~+ s会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
    & |" t- S7 _6 F6 P  h4 K" q! t8 e4 w4 d; v: q+ a1 p: p
    会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。. C2 L  h7 G4 h# @6 o

    3 B+ q1 z/ r) R2 D  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:5 b4 D. \. W) V
    - _3 B9 O$ M. s3 q
    概念        单元素类型        数组类型        pandas数据类型. E# x7 ~1 t! K7 u6 F
    Date times        Timestamp        DatetimeIndex        datetime64[ns]
    & A' o1 e- i5 r$ z4 ~& m+ _$ {Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]1 v$ y3 S) v' b/ j
    Time spans        Period        PeriodIndex        period[freq]/ d1 F1 f4 o" l' n
    Date offsets        DateOffset        None        None  k, D5 m7 p7 k! j% Y! G- L
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    % a- U1 p( n5 X6 ~& x: B* d5 I, o! l- W
    10.2 时间戳
    & ?. v9 u) p: V( t$ C0 `10.2.1 Timestamp的构造与属性
    9 T3 }+ ?8 R  K! W4 |单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    * c- |0 g- j$ F+ n; j# K" N- t
    ts = pd.Timestamp('2020/1/1')
    5 T# j# r4 V/ v  G9 g1 v- @
    / M6 A" b* C  ^8 R& Ats
    0 t" V) u6 q8 f5 q' EOut[4]: Timestamp('2020-01-01 00:00:00')
    5 j0 H2 U5 s$ i0 U! i6 m( \* l& T& j  R
    ts = pd.Timestamp('2020-1-1 08:10:30')' z" @4 i/ F- s' e) r
    8 u2 z3 b. c8 Z+ x9 |
    ts4 x% {2 G/ p) Z! z( l
    Out[6]: Timestamp('2020-01-01 08:10:30')& m2 P# r! A5 h. ^- q! s3 a4 K
    1( I# R) d$ s$ X" B
    26 `$ ~3 @/ T% `4 T# t
    3- B- W# }1 N( t' K
    44 N  A& U: ]: `2 S& B. ]( g) @
    5
    % o2 R* j5 B9 p) z1 h' ?  w6
    * E  y( Z9 _) t5 m: `$ w/ Q7# k7 q9 [9 ]0 R. r, ^5 g) e
    87 a1 u4 V6 ]# d6 N" w/ ~& k1 q
    9
    ! z7 e% C4 c7 H* y" U: a* P通过year, month, day, hour, min, second可以获取具体的数值:
    2 K8 f$ U6 i$ |0 t, r* W& c
    # K# n- V8 X0 V; f3 |ts.year
    & C6 o( v# Z4 i. g% EOut[7]: 2020
    / n, w0 O" K  ]  s
    3 s) \  v6 m) A  q% ?: tts.month
    / Q1 F' O0 R' B/ VOut[8]: 12 B* ~: G. l' v2 b( y
    , q7 C( f  B" S
    ts.day
    / f& B% e  {& @8 K9 {5 G+ nOut[9]: 12 B: J6 e4 O3 [$ Y! k

    + G, Q+ ^' C, ]- v1 \: z0 Ats.hour
    / W8 C  e4 H6 Q8 {% dOut[10]: 8
    7 D# t! E* F8 C# H% R* a+ k  a& O& ^1 L- n6 B
    ts.minute* g. s7 g  v1 j- n* r2 }
    Out[11]: 10
    # C3 {% X8 C( ?1 y* c  B5 Z0 E; Z4 K, m; \. g  ~
    ts.second
    2 T  Q3 p3 A9 ~. ^Out[12]: 306 ]8 s$ \2 |9 B$ m, n" Z9 a( G: D
    & m- q" b" I( c2 x) n+ S" F/ _
    1
    $ W0 G3 [, R: x% O- T) G5 y2
    1 {4 H. r& ^9 @' z1 t3' }3 j( o1 Y. M7 i# w% e8 Z: U
    4
    * h5 _! v3 M) \( t% N5
    $ \# y* }" [# o5 t$ h9 c6
    0 B( O, H7 a8 r1 p; N7
    % ~) P5 z& `7 ?8 B, g1 S8
    : F9 l6 @. H% f% x; ~# d9
    . O- W/ w  F0 @8 k. H& Q3 n. `5 l10- j: N1 E" S6 V) [, k3 W
    11
    ! H, o$ E+ _) J, @  m( O12
    ! S) c" X; c/ o5 N13
    ! H7 t# G. y( ^, o1 k4 J14
    " j6 G/ |& h' B$ K15
    / n! U- Y4 v% Z) g% F, V% p/ o4 V16
    2 T4 N& W; a' y" H2 x& S6 a17- _5 U5 n$ U% c; T4 w- w6 u8 |/ S
    # 获取当前时间
    * w6 p* i0 a* z) }' d% `4 Q: P& ?now=pd.Timestamp.now()7 ^, c8 ?! R# H4 ^  ^( e
    1
    ; \# c' K, s" p3 m2
    % ^5 L1 o3 }: P$ \在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:1 |. R1 `' `3 M: n% I7 ]. A
    T 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)
    % I+ f4 C, ~5 T" k3 b) a% eTimeRange=
    & P5 p) ~8 H* c/ a" o; Q10 . j! T+ H, w4 G: S9 v
    9$ b5 e: N* F4 s) \. m# q
    ×60×60×24×365
    & }" u2 _1 a7 Z2
    ) c3 j5 `% \6 o( t64+ H9 U3 s9 @; k2 n# T9 Q

    6 a3 Z8 N2 w) x, R, G$ r0 D( a+ g1 i3 q% ]
    ≈585(Years)
    , P# s# N5 X' f/ K% Z# k* l" V0 L
    0 Y+ Q+ `$ J7 X$ H" G通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    ( s! T; E8 u0 d: l2 @( A% O. v* ]" V4 c
    pd.Timestamp.max
    ( p; Z' P+ V4 uOut[13]: Timestamp('2262-04-11 23:47:16.854775807')
    8 x0 T$ Y) q' U8 P+ _' G! Q7 Q
    : E* U; `! S7 }3 epd.Timestamp.min6 C& B5 n6 e* S# P9 P- v% |$ ]0 B
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')
    . ~. B' [6 Q' }: J" j; G+ E4 \, E( _( N$ e; j
    pd.Timestamp.max.year - pd.Timestamp.min.year
    8 o7 y( J- G5 R+ x1 m+ ^Out[15]: 585
    4 D& g( o- w/ C4 O! a( P4 \2 r$ a1, k7 m+ |3 I7 e1 J. x
    2
    & y7 N& m2 j8 \8 U% G3
    6 M7 @& B3 k) `' N( @' ]4' u& ?# V( ]3 v1 y& `4 n8 D
    5
      o* u6 Y" J) I% i) H) W7 G' P6
    ( n7 T! Y. Z* L" k2 D70 K" d  H: V5 ^) G5 m1 t
    8) l& @  Z# z7 R( q
    10.2.2 Datetime序列的生成) p6 I, G" j/ u: p* [' l
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    / D9 J7 y) }7 F9 v! Y                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    : |0 @7 I- q* P) W' ^, C9 {- C& t1
    9 \. N% I' `7 H) ~4 O2
    ' S) L7 e; i9 ]+ X: r6 Zpandas.to_datetime将arg转换为日期时间。& o4 t8 Y& M4 A' {
    ) d: L; A/ q( r' q7 O5 ?8 j
    arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。8 ?% y2 h: b2 m9 Y
    errors:
    6 P. \+ n7 A1 y8 Z: M$ B7 E. }- ‘raise’:默认值,无效解析将引发异常3 W4 x, I% d7 q5 j& ~1 z
    - ‘raise’:无效解析将返回输入& `- N$ O$ t; \; f
    - ‘coerce’:无效解析将被设置为NaT
    ! ?* A) O7 w; \7 _dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。2 u+ {0 `# J/ y2 M
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)3 @7 x5 i$ r9 A2 G
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档$ h5 y3 o3 o" s0 v! Y- v
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。- A* I- g* ]1 _" c2 @6 l
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    & M- H9 {! D5 E! Q& J* d3 s" Xto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    0 O$ {  H/ O6 m, c, opd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6']); t( G- |8 T6 o

    . @4 ]. c3 u) d& G1 n5 iDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)' W9 e6 ?1 j# k! S+ H$ o
    1
    5 I6 t* ^( `4 h2
    % i$ P+ D1 V  [* Z3
    ( ?+ `* J( L: c8 x  X1 V6 r在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    ; H! s7 z+ x3 |: y' g- o0 V! p5 P; M& l( R3 x1 @  O
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    0 c. D7 `' Z+ z, X2 }3 d" e% [7 l6 Mtemp
    , M6 Q6 @6 Z0 g" z# Z0 u. |
    0 D+ }, g# C0 @# o8 W$ z/ T, y* G( ADatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    9 ?4 ]+ k" d# C, A# X1
    - ]0 A, K% N! i2 N/ ]4 m2
    + ^4 W3 W) C$ w- ^( X3" e( N' [0 U9 v  [
    4
    5 m: F. A# c% B7 a, R4 P  {$ F  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:, c9 Z- p8 _3 g6 h( t+ _
    + U( g8 t" S: b! E: H
    pd.Series(temp).head()
    4 s% k! q# D7 K  M+ A' U  k- Y. e0 r9 ^, x* N
    0   2020-01-01
    * F7 e9 T" b/ c5 P9 I# h1 o1   2020-01-03" c. o" t$ Y7 v* C( c9 B
    dtype: datetime64[ns]* N8 v" _; a5 v5 |/ s
    19 n- a: Y8 r9 e
    2
    , ?7 X. R$ D" G4 [7 H  h+ N3# j% Y6 r) F# R  W
    44 v4 f# A* g8 x7 Z3 a: o+ G. d
    56 w" D2 ]* \( V+ D  T7 A
    下面的序列本身就是Series,所以不需要再转化。- N4 T5 v/ E6 ^. T. [7 Z

    % P( _) b  Y' X3 t" V9 adf = pd.read_csv('../data/learn_pandas.csv')( N: M/ z# `! O: a* x: g) I
    s = pd.to_datetime(df.Test_Date)
    + D: ^9 }5 M, Q0 ]& L- Zs.head()3 I% j7 H0 y$ b$ F* D8 ~" v6 Q
    5 K. l8 B' Y) z% C: g# i
    0   2019-10-05
    9 |% z) i" `) o0 T7 O1   2019-09-04
    " x9 [* L7 G* h* q# ^2   2019-09-12$ _7 s( F' U# \. o! Z9 |
    3   2020-01-03# k$ `5 m- m( A3 y; R
    4   2019-11-06
    8 {; D' L9 S, R+ D9 I' lName: Test_Date, dtype: datetime64[ns]) m# m4 B3 M( ^. H. _/ M
    1
    # K" ~8 }) ~$ D" b: p+ A2
    0 X7 f* R3 P1 c; G) h3
    ! R5 b7 k% }8 W& q1 e6 r$ `4
    5 z) d3 K0 b0 a52 |- j/ @2 v8 {7 d4 u1 y5 q" K
    62 a6 I: Q% ^7 q( @+ E" J' E
    7
    ; z  I5 Z7 E6 ]% V( ?5 ]* o' w8" y1 |2 {4 K3 d* f7 z; L7 B0 v* N1 ]
    9: l- G& J' p; P5 X6 t
    10
    5 S% R$ u% ~4 y- J" m1 C; u3 ?# _把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
    1 m! ^- v2 W0 D% m9 |df_date_cols = pd.DataFrame({'year': [2020, 2020],6 ~+ I, [+ ?* j4 f4 d& n) ~
                                 'month': [1, 1],
      x/ N$ W+ e0 Z8 `+ Q! B' B                             'day': [1, 2],# n; _9 R. {- c
                                 'hour': [10, 20],
    & q6 g+ m. ?0 m! `5 j( r5 u0 ^/ m                             'minute': [30, 50],
    & d, i+ v( ]* w$ f' k9 }$ B! T' d1 a                             'second': [20, 40]})
    4 b/ p; c3 Y8 S& u: b, lpd.to_datetime(df_date_cols)' b  \- o7 \  W* ?1 B
    3 Y+ q: a% T) O& e9 j$ w
    0   2020-01-01 10:30:20
    ( ~1 m0 ?7 L. n$ c4 u& _1   2020-01-02 20:50:40' R' m9 O) z4 ~" i2 C' F8 b
    dtype: datetime64[ns]# l" d: N* p  J* ^) [
    1
    . X0 f. t/ @6 L2
    7 Q9 X! e2 j' L" p+ M0 S3
    ( N4 w8 ~$ `2 Y+ ^49 W6 c( [! A2 R7 i, @
    5- i3 O8 B( l( I
    6% H8 U8 I1 D& B. p! W+ k$ B! }
    7
    ) @1 r0 N* m/ z9 W3 o- P8
    9 o; |" v2 l1 ^. \, J! g- `92 Y" s* t" ^2 p8 R! Q6 z
    10! m' K! z; l3 t  M8 i
    110 f0 G. s4 z: n. B
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    * `( \" J3 F$ b$ b- g8 ]9 q4 Ipd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    6 \: Q& j+ x: vOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    ) |# l8 U  N) e) r. C* B
    $ W/ \* V! r- T+ Spd.date_range('2020-1-1','2020-2-28', freq='10D')# |+ ?- H1 v. Z: S5 |5 q
    Out[26]:
    $ |" y, Q6 t; yDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',* u* N; q' B1 f7 A% z0 i2 H
                   '2020-02-10', '2020-02-20'],
    9 B! ~4 w  Q1 \( G( u              dtype='datetime64[ns]', freq='10D')1 q9 W& t/ A8 |; J: B
    3 C0 B9 ^5 B/ [5 N4 c; p. H( H4 F
    pd.date_range('2020-1-1',
    . A- D& c& S3 Y; ]              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天- e/ C+ \2 B$ n4 x# n% C# e5 m

    1 R! }7 c, r# w4 @" ]Out[27]: * U) [7 c1 `% p9 Y* V( ~4 B
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',8 Y; N, K3 h5 z4 O% ^. e
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',
    ) n/ g0 j$ }- g               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    - R3 w& U: P1 \# i: F              dtype='datetime64[ns]', freq=None)
    5 j1 k6 N# J0 d4 }3 H) }1 K+ G6 j' u6 v2 d# Z
    1
    ( ~" n3 ~9 R; I! q. s8 L2
    & h( E) \) V) i3
    ( V% T9 o- z+ H+ P4 J4+ G  b  }5 c7 s- t
    5; @( L- w. b, _  z' r2 d  a
    63 g/ Y1 a" X, _. d! R: r+ E/ K
    71 z- m2 y1 v* s9 H+ d
    88 h0 K6 ?% w7 t
    95 q" m) g. K  j  |8 c" B
    10$ t1 Q1 h! A) z6 V' m, t# T* ~; x
    11
    : t2 b- A0 D7 u: O12. F% K6 _% @) X' Z5 z" |3 E
    13" c. U& g& |& z+ c. ~4 ?  R( E' v
    14& |2 G( G/ _! l4 Q; G
    153 Z9 p' P' F+ x* \8 o
    16- _* b' Y! m0 J/ a; ?* Y7 L
    172 X( K$ v- E* ]
    这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。! N3 K, }' a4 S- N. K! {/ M. _! J
    # H$ ]0 }- g+ c- B( i# B- @5 Y8 t
    【练一练】
    - J4 N( s9 R# A7 E- G% \' k: _7 f% eTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。% _, C: E' R4 y0 C

    ( y; @9 a/ ?2 ^; H! dls=['2020-01-01','2020-02-20']/ R. C) U/ ^; S; r' @
    def dates(ls,n):0 o8 h3 k/ j) c. [/ z2 Z
        min=pd.Timestamp(ls[0]).value/10**9
    : c( O$ q# j% L7 g    max=pd.Timestamp(ls[1]).value/10**9
      s+ u$ A+ l, B0 i9 n  {    times=np.random.randint(min,max+1,n)! `/ }( h: p( B( ~2 {* Q' L; @7 {
        return  pd.to_datetime(times,unit='s')/ m5 ?" _* ?+ B  Q$ e1 @, `) K
    dates(ls,10)
    8 ~% Y5 j: x# o- q5 |/ ]% X* F, J3 H( \7 r0 p% ?7 W
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    4 P- D# x  {& i2 a' t: b               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    8 B6 d& L& H# E5 Z               '2020-02-15 00:18:33', '2020-02-11 02:18:07',, A% F4 z* @$ J4 \: H6 D# ?  L: G
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    ) g8 ?% _0 B7 v3 e               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],4 A$ U# Y2 F7 M4 A& e. Y
                  dtype='datetime64[ns]', freq=None)) F: E3 `% C$ o) M8 ]2 T7 T* L) E+ F/ S) A
    1) r* Q: S5 ]2 A: _
    2
    ; L0 M$ V4 c, m2 B37 u4 Q) i) v2 Q( T6 u1 I! M( _" y: g: }
    4
    * _* \- _4 [7 C! E8 U- P53 u0 ^/ d% p3 s
    68 b2 p9 u& Y- x4 t% h
    7% @+ [" ]' @1 k+ k9 ?/ ~. e: s1 e9 l
    83 n6 G5 R) Y& i8 c3 ^' Q6 _2 @
    9
    ( q3 n% ?6 [% L5 T* s0 x10
    9 {2 R5 D) c1 J/ t11$ T  ]$ A& V! o, e2 @( I
    12) O6 b! I+ K5 E$ \2 v" ]8 n  X
    13
    * v: m' y- d/ E4 B141 d  M4 Z8 V& d9 |" |( A: E
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:' y2 B0 ~: H7 i6 J7 `& u3 M
    s = pd.Series(np.random.rand(5),
    : w9 A& w# q* d! D% @4 V, b% }" W            index=pd.to_datetime([
    # T6 w) B* y- m/ K5 t                '2020-1-%d'%i for i in range(1,10,2)]))7 L2 R6 x$ u1 z' P1 L
    ! D- d9 G- d" P

    - P6 n% c* H" |s.head(); L$ w7 `* _8 U4 \& p: `
    Out[29]:
    / ]; `; @/ c5 ?+ |2020-01-01    0.8365784 a- b' B* [6 T
    2020-01-03    0.678419
    * [& Y" N+ O& {& g2020-01-05    0.7118979 o" P8 Z3 ~, H) o
    2020-01-07    0.487429
    . x4 Y3 h; @. p$ ~3 G2020-01-09    0.604705
    , O8 A9 K8 j0 A2 k" Z- D5 {9 Ydtype: float64
    : V! V; i' ?3 s1 ?( P3 ]# y' E( X0 U. m6 s6 H7 e3 B9 V/ r
    s.asfreq('D').head(). y" G, q: e. E. l! f! j) H) |
    Out[30]:
    6 {1 r' |* u( Y2 a7 H2020-01-01    0.836578
    % l) C8 s: a) R, ?( @. F2020-01-02         NaN
    0 Z5 |; K% x# K% @2020-01-03    0.678419
    4 J. G% r& Z3 k" `- N2020-01-04         NaN
    ) I- _) g5 w9 S1 f2020-01-05    0.711897* \( x  L5 y  F/ f; |( y& p8 p
    Freq: D, dtype: float64' B* z* |  z* K! a2 B+ s

    * e" q' y3 ]& |- ls.asfreq('12H').head()& Z! `/ L7 S, L( s, w' v6 p
    Out[31]:
    5 Y- t) i; g9 B& h, Q8 r3 h) _2020-01-01 00:00:00    0.836578
    ( R% B! L: {  N6 b2020-01-01 12:00:00         NaN
    1 z& Q% I) n4 `  H: t7 O5 ?! O2020-01-02 00:00:00         NaN
    4 F) f- d8 p3 Z5 b: L4 n1 L2 R2020-01-02 12:00:00         NaN
    1 {/ Y3 z; S# w4 m7 B2020-01-03 00:00:00    0.678419
    & m7 s8 t  I1 r2 P; PFreq: 12H, dtype: float64& \+ }/ W+ R& X& ?: B& g( C

    : m- j: ?2 v: y0 e& P* ^1" H# A2 _8 i- Y1 B& X
    2
    6 p* |+ \% H: z3' k7 A# W. s9 s0 l6 F) I& c" z, a
    4
    1 O3 N. x6 }* O; E5- z  s6 I' z% K+ v
    6
    ' N, a& e3 j9 p; s* C8 p7
    # q% \% i9 A% v5 N' G, {8 V! d8
    : B3 J2 ^3 a6 k! ~  i0 [97 }7 t9 D& e6 w# D; J5 J1 x* E
    102 N2 m8 C. C" H. k+ m* x/ Q0 {
    11
    / O+ h1 v1 J3 ?5 V1 k121 Z9 q" \. G4 }8 z  Q
    13
    : `7 |) ^* T3 J, {  T5 G; h2 u! a14
    7 M. P+ g/ I, t$ I( F' q# k& p15: K" P: ~; [% P$ K" O# A
    164 x+ `, z& _$ N9 u9 m( Z6 \1 S
    17
    2 y. r- X5 p! K1 d. G183 M  {3 h' w4 C  l. D% u
    19* y1 k' g3 L) f
    20' Z- T; q4 A1 a1 }/ Q6 L% A5 B1 L9 U
    216 I' s& Y, d( h3 X7 z: q
    22
    / i  Q  E. p/ |, t1 D! m+ j23% L, ?: ~, G! C7 P$ t! G5 r
    24) C, l: m  z5 O! a
    25
    ) j, U! J0 d8 d+ e  t! J9 ~" F26
    : j* E% f7 h. a4 G, n" |. _0 e27$ W; Q9 A+ m, |( ~. U
    28
    ) \5 z; H' S. K* P6 Y9 V& _, \29
    ) n! U$ Z) t7 x3 Z30
    ( [1 V3 f! ^& q! [3 l! q1 P( @31/ W8 h6 \3 f0 _( T0 _
    【NOTE】datetime64[ns] 序列的极值与均值
    7 u4 V0 B7 ~) c# x- I" u$ O  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    6 \/ f3 S: t8 u
    6 ~4 Y* @: X  r- i7 j10.2.3 dt对象
    ! l5 W& {0 ], k3 t9 ^6 \6 T  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    + [/ i) e( q# V+ m, R
    : ]8 J7 l0 u8 w& n第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。( N7 v$ U& z' z3 }+ u
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    ! s5 t* k; n, h& N
    8 k. h' D9 B% b1 Q) a5 Ks.dt.date/ U7 D" j; F$ w6 S% a1 Z6 R
    Out[33]:
    4 b0 W0 Q5 j3 x3 G0    2020-01-01
    / R$ Z% R1 O. K1    2020-01-02' x0 `7 Y$ r' i. m5 n
    2    2020-01-03
    " P. n1 W( Y) W7 q' @$ }( Tdtype: object
    9 y4 g  h0 d4 ?, k2 [4 T0 _7 n$ n% J# n/ k8 D
    s.dt.time, p7 L7 X7 c; i9 A. Z
    Out[34]:
    ' k# E. C7 p0 w) V* g0    00:00:00
    2 ^2 z% ]9 i3 g1    00:00:002 B& F2 e- a/ i6 n
    2    00:00:00
    / \/ B/ d% S4 s: F4 K0 l% p% {dtype: object7 ^) e& g/ j- ~/ Z# N, h0 K

    , L  }8 }" f) K% _8 J1 U. Ls.dt.day
    " t" k" |8 ^$ e+ N* D6 d4 p" yOut[35]: 2 e4 G3 }: P, [+ i7 K% }" n
    0    1/ c7 X# P  A" \% `- k- I
    1    2: g! S! K- G$ `" l+ l
    2    3
    / ^7 s1 Y6 x  |% N; D% f! hdtype: int64& N+ H. a. H6 D' v" B

    * e8 I. Y( G; ]1 k6 Ks.dt.daysinmonth
    / }0 @/ E3 n( O( n5 S5 kOut[36]: + D' W* O2 A/ ]8 V$ v  x
    0    31( Y; t& r; s, I9 a5 S5 x  `
    1    31
    7 \' C- |1 p$ ^. g/ a/ x3 Q2    31% {; X2 w: V9 `3 O
    dtype: int64
    : z4 t% J8 `9 [0 h: E" p8 \: d
    , x8 x3 ~: ^4 w5 V+ o& C% F11 F; h8 ]( D4 E" D- `
    2
    & Y4 V9 }# [' H+ B8 ^0 i/ z& e3
    : g% F6 h. r' [1 z4
    ) t2 z! g% S0 |: @6 T55 B5 k& \, r7 g
    6+ {2 P/ D7 R/ [- v# q) s
    7
    2 d: z' }/ J( {7 D$ M8 t+ P8: f: l) }' ?1 |; v: L
    91 t9 M4 J% U% e# P7 S/ T, H
    10
    6 H% Q, ]; J9 Z5 D& H% X115 t/ B! X* x5 C
    123 e$ V$ k  P; z) E5 \
    13
    ' B8 u' x* I- f+ |14
    3 A( z0 _" c0 T- y0 R15
    / T5 N& I  @" {- L! H16
    1 g7 H( U1 J. H. \17- @3 o( S3 l: J9 V1 C
    181 `# A$ p# e9 k/ i7 E: }
    19
      G. D+ v6 c5 k( O# ^: N20
    - X! F7 ^. ]# r7 E8 l, t! G7 B, o21, w$ V" @/ u" q( S- m; e
    22- [$ Y5 H, }7 a4 [; ?) Z
    23
    1 g! ?& D7 a3 X. E24  A, `, k+ M' E% g& u! s
    25
    # R% I' z' l: n/ A* P* [26) ?5 }7 G  a- v" P
    27
    ( }: j4 \/ w( u1 P) D  e( u28) E; ^* R$ F  M# l+ u
    29
    ' Q! y9 L5 R4 T' _  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
    2 S2 j+ ]- M- l$ Q5 P7 B6 ?0 s9 r: S2 d; y$ Q; P3 n8 |
    s.dt.dayofweek
    7 A2 H5 h2 k; b  ~, Z- ~% c) TOut[37]: : Q9 i" I; F$ O. B
    0    2
    $ I( X3 o- ?; A! C3 M$ X1    3+ {3 p6 ~3 f) X) v$ g' P* f
    2    4: \; x  i* A; \6 N* [
    dtype: int64
    . s' r/ n7 y+ u3 ?' [8 a0 |( Q$ \* i0 Y( M
    s.dt.month_name()
    6 A1 e; H9 d- ]1 s/ O6 EOut[38]: % @: K" u# K  Z
    0    January
    # q9 S5 \* F& `" v; J8 x1    January
    : Z9 v6 o/ G+ n% }2 Q0 ]& N2    January5 i# {6 X; f( P, `/ `$ \. R! }9 j
    dtype: object
    8 ]$ r9 u) B, X, ^: r
    / ~- o1 T0 _- p  p8 [8 Zs.dt.day_name()
    / J7 c6 Z/ h$ y: Y. c9 a1 K4 Q* N9 wOut[39]: 8 @  P7 a6 ]# q3 K
    0    Wednesday: }. e& F4 _, q# G9 k9 r6 R
    1     Thursday
    $ e: Q* I& P) C0 [( I2       Friday& _; p0 `7 |& K6 M& A9 m
    dtype: object
      U" {, b+ w" }$ o$ Z0 U" R+ X' S7 Y- y5 b$ J
    1
      {9 O. f6 `, T( L' p& }2& l# r; ~  ]3 ^* e0 L0 x9 Z8 S# p
    3# }5 u: L4 [( F/ |5 J
    44 I+ G9 p; O& h0 x4 Q" x8 U
    5
    # W2 R! ^9 ?' l5 t3 X4 c7 `' W6
    $ ~0 `5 u; z+ r: c" `# {7 X% Z2 D7
    0 k+ I3 C) d; u4 Y" I: j2 S2 D8
    - @- j$ s; o7 U; V; ]% P+ j3 o/ b9 K9
    & W7 I& U1 S' |  ~10
    ) M8 q* a' y- A118 {. \# ]  s7 n. s
    12
    ' p% ?. l. r5 X/ t6 n& C" j13
    ) x/ z& y% i$ c: H) q14
    . n& G  S% r4 A15
    # }+ [& B, f; p8 q16! O/ U2 r5 t: F
    17& B" ~+ i* {& O( B3 B0 P) l4 t
    18
    ) \/ Z. k7 v% a3 B4 k* e19
    * V7 u/ V, R! M  s0 g# E7 h20! W: r* B: K- ~6 y; v/ K+ p  l
    第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:! `" q& k8 C! x% M) V* F
    s.dt.is_year_start # 还可选 is_quarter/month_start
    - [- e/ D) r0 c6 M" cOut[40]: 0 Q* a! y3 t8 i& r2 ]( {! b, \+ T
    0     True5 M0 P8 y' y- Y  G+ O. p1 c
    1    False
    # j4 d( n  h9 x) k: m. {2    False% q3 j- N5 J8 d& v6 A& j4 X
    dtype: bool
    ) ]$ u3 f1 Q' O& U& U6 c, ]! t% r# ^! m
    s.dt.is_year_end # 还可选 is_quarter/month_end; v& Z# N+ Q( F
    Out[41]: ! R4 L0 q! i7 {, `. a$ b" V" U6 @, g" W
    0    False
    2 \* b7 A2 n; ]% |4 L1    False/ ?" K( Z% R: S8 ]4 a
    2    False
      R1 r6 c" N5 Vdtype: bool
    9 _2 A- q' u6 u& I1
    9 m5 ?$ J8 x. k5 |. a1 _2 Y4 l% p: l2  @0 a' D& [! I' I9 x
    30 y* f' H  |2 E- p
    45 f' Y) O# o# f8 s6 ]/ K0 V! l
    5
    ' _* q4 H) F1 W# ~) A; |' j8 i4 W6
    2 h8 U1 |3 V+ w, C  M, X. [7
    + @3 \/ q; g+ W# ^" ~" J1 [88 b8 v5 |6 {& T6 X% l" g
    9
    " l: Y* C4 R; H/ n10. k4 M0 p4 s' z0 F
    11
      ~4 z% f6 w3 `1 q0 H12
    ! f0 C/ n, Q" ~3 ]/ O5 o# T0 q+ Q13# v! d: Z& V& y8 L/ G0 p
    第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    1 Z$ p0 v4 L3 ^6 y; d: ws = pd.Series(pd.date_range('2020-1-1 20:35:00',
      B# @3 n+ i8 i' G( i                            '2020-1-1 22:35:00',
    - A$ h5 D3 ^4 _" D# D9 c% X                            freq='45min'))
    ( Z; p8 E5 `6 O4 X
      a2 h8 s  O9 D6 B: v2 x/ V1 e, r/ o8 [& v
    s
      n2 u4 {) l5 d1 d5 f* GOut[43]:
    , ^( l7 ^( t# Q. ~2 X0   2020-01-01 20:35:00
    9 Z/ T* o, G2 d1   2020-01-01 21:20:00. N4 @6 t8 e) _- r7 R6 U% Z4 G, @
    2   2020-01-01 22:05:00
    , I$ l5 t7 ?+ k! ]% E( g8 Xdtype: datetime64[ns]+ B; ^  B# L- ?' N

    " g) I, Q, |9 M) ~, L2 as.dt.round('1H')
      R+ {2 [+ @( w* ~. iOut[44]:
    $ K3 x, ?# @3 `3 j  W! y: D0   2020-01-01 21:00:00
    ; p& Z" q0 ]1 |  s2 F$ r1   2020-01-01 21:00:00
    + N8 N- d- p0 d$ Q( u. R% v2   2020-01-01 22:00:005 H# t& J4 X# F" v  |* ^* }
    dtype: datetime64[ns]9 m0 T% t9 K# b% I) \
    3 ^& `) V; s: ^% [, N
    s.dt.ceil('1H')
    3 ^$ q& e3 u# b3 XOut[45]: $ B0 N! l% C; @# L# x- c* W
    0   2020-01-01 21:00:00
    , G, w$ U( X+ ]. q/ Q1   2020-01-01 22:00:00# ^- O, ~, _" X% w1 \
    2   2020-01-01 23:00:00
    3 o7 q# i% E: A5 t$ f7 {dtype: datetime64[ns]
      X6 l* D2 k0 J# B8 F, Y
    + ~6 g, P, N- U2 V5 |0 m% m2 E5 R3 As.dt.floor('1H')
    ) }& V( ?! s. w1 u6 U9 ~! ~& ]Out[46]: ) g9 p( H7 p' }( ]' T
    0   2020-01-01 20:00:008 M4 u, l+ F+ `5 L
    1   2020-01-01 21:00:00
    1 S- m) T' p; t3 w2   2020-01-01 22:00:00& N5 O" @7 i0 m' [: K8 O* z
    dtype: datetime64[ns]- F" }3 m1 _7 B: C( G' Z
    * a9 f6 Y9 X( N6 `  ~
    14 z8 d5 o7 C* E# L4 O) a+ B
    2" ^) L  v: n/ o) u% j
    3( X% `4 E: P* J. r' \* L: t
    4- k% S5 F  |# o& O4 ^
    5
    9 w6 W+ b- ]$ V$ ?6% t' j# T( }, w+ p2 l% Y
    77 b( r* _3 x1 y' {" ^
    8
    * T. A) \1 H+ j& g( d9
    7 m  W) `0 ^" H104 K1 h# q- S* y: A3 v. m# N) m
    11
    0 i4 \5 k* W2 h" K4 P/ q12
    $ ]; T* Q; ~$ ]. _/ y13* r  T( ]4 [8 [" P
    148 B0 r" n, S- z2 q2 M" @% q
    158 Q8 d; ]% {( u+ F1 ]9 M
    16
    : ], O" g2 T& a) S( A17
    : {* d- s/ D% R( e. P, G: D18+ E% W. B8 M9 W9 j& }
    190 ?( T8 H. p7 g, q) E; v$ D% N
    20
    2 P7 \1 a. b8 C8 A& W% _21% r* a" s) a. H2 `
    22. c; p" J& u$ b3 U" f+ R* ?
    23& y+ D) X& @+ m! P' y7 L: m
    245 e/ @7 @3 u% Z4 C8 h& U$ W. {
    25' i% j( o) |# z% }: }
    26
    3 t  n/ M7 J  G6 F3 S27% Z% j' v/ n' e! e4 a
    282 Z' c" Y" j, k& f) W7 ]. y
    29
    2 ^, X1 X- I/ O/ O' T3 z30) w5 N& t) X: w- D# P$ c1 B
    31# R6 p2 p) A; u
    32$ I5 q  W) K- f, a$ C/ y, f- F
    10.2.4 时间戳的切片与索引- L% \0 l$ l1 A1 I
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:, V7 T- A' e. t* ?% x; I

    2 D0 ]. P) s4 u  m* n1 p利用dt对象和布尔条件联合使用
    + x" D. _1 x' s利用切片,后者常用于连续时间戳。. ]  b8 n% @9 M& [) M! D
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    8 V# U) V# m9 P1 v0 @  xidx = pd.Series(s.index).dt' a+ C, y' p: E' W- \4 H* J
    s.head()4 O" d" I/ H- r  G
    + p* J5 o7 F: M9 r* j0 S
    2020-01-01    0" Y' J& f4 g$ ]/ ~
    2020-01-02    1$ u" `  d3 ]' G% J# {( r
    2020-01-03    1, t# F# k# I7 b
    2020-01-04    0
    + c0 s) C4 I8 \1 Z# N2020-01-05    01 b9 V' ~% o7 |9 j" n2 i% d6 }
    Freq: D, dtype: int32' D6 a: }- N, |; M' h
    1
    5 D2 s! O9 |4 U6 z/ a2
    & ~$ b$ C  Q/ t" g7 E+ h3$ A4 b8 D  P% t
    4
    ; {- T. O, _% f& x9 f5
    ' S) ~& L& R  i# B6% v: R! b1 v8 b2 V
    70 Z5 s4 D; R9 x) q) V
    8
    ! V( V8 b) F8 _9$ \1 I6 f& C2 k( H4 L6 h3 k5 a
    10+ h# G; g( s( m- N
    Example1:每月的第一天或者最后一天
    7 `( `* i$ A7 O1 O
    8 V1 x7 M5 Z3 |0 V" h" T( `s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values* G' D8 r  n$ j! g6 G; q8 b
    Out[50]:
    9 }& [7 N/ C& p" Q2 x2020-01-01    1. G6 t2 o1 m! ]; C
    2020-01-31    0( j; W2 e- e7 r+ D3 F
    2020-02-01    1
      n. ?" ^6 z8 V. V$ r2020-02-29    1- D: i# r$ f+ i% `$ k/ Y. p
    2020-03-01    07 G; h% k' Z5 Z
    dtype: int32
    ) L! i2 H, F7 V1 w! L1
    # t+ m0 e+ `* v0 P21 W. k" h8 q3 J7 D
    3; ^' n0 N3 G0 [
    4& D: d1 `; ^5 R$ C# h) S1 n, C
    5
    4 ], _# p$ c% o. {) [5 y/ B6: Y) K$ p( {3 C
    7
    + e, b; c5 s/ y; @6 w, }+ a8 i6 p8
    2 @/ {4 \% R# {Example2:双休日- {/ ^) x+ |  a6 ^) P4 u

    . v1 Z8 t; v2 z% W  \( us[idx.dayofweek.isin([5,6]).values].head()
    " p" R; _( J0 g9 dOut[51]: , L9 i2 [# n7 h- f; D  V" o+ l
    2020-01-04    1
    & G$ Y& C0 v# o2020-01-05    0
    ; q) L5 d0 {* P0 Z2020-01-11    0, N* g" \) E; A
    2020-01-12    1: y5 t, F% }" H+ v$ T3 ?( @
    2020-01-18    1
    , y( k; S4 N/ @' k( X" l. e3 A0 ]* Ndtype: int32
    . _) v: [, h/ ^( ~, d0 O1- k. a; E4 j7 ~
    2) \) l5 \  q* D! E
    3
    5 x, o6 a: w, ^6 y! |( r' z* F6 q4
    0 r2 P2 X7 D7 l  p56 C  }( h4 }& B4 Y/ s8 @3 J
    6# L% e4 J! i+ e& R2 O0 t, a  ]
    7" [4 r# K" ]0 R  w
    87 `' p1 H8 D, \% {
    Example3:取出单日值% |$ H: K3 r" _2 U* c+ }$ c

    3 E. a  X1 _( M7 e% H" J. u$ g, G3 ?' ds['2020-01-01']
    1 I& P5 q8 V( j% L" Q/ yOut[52]: 1
    ) s" {; m9 ~) q% D2 {) o5 E; L$ J' _& Z) y5 u
    s['20200101'] # 自动转换标准格式4 [6 L: u3 t/ d, a$ x7 k4 `" q
    Out[53]: 1$ x3 D  ?0 n  w2 W  s9 Z  A1 S3 k
    1# u, f# C; Y. {, i' I+ U$ }
    2* {1 p7 O+ A% |2 u8 l+ D* M3 l
    3
    9 t" S5 T# t' @: S9 X2 l4
    0 Q. f% z1 R6 j/ f  b  I: v5
    4 n: O8 W; x! kExample4:取出七月% Y0 S& Y& N8 J. U1 o8 O( {
    5 `5 A' s' E: S5 q2 w- m' [# U
    s['2020-07'].head()
    8 E6 U# D. u5 T0 H7 O  uOut[54]:
    7 G5 H3 }0 n9 z$ Q1 t$ s2020-07-01    0
    2 o* X" t( b+ K% E, l3 F. P2020-07-02    1# i3 t1 a" H- F1 S- w% ]
    2020-07-03    0
    0 A) l) a6 v1 M$ [. @0 P! Z+ L2020-07-04    0  f9 t! Z7 {8 f6 Z/ O) ?
    2020-07-05    04 t1 L' T* @3 A* G- D1 U1 X
    Freq: D, dtype: int321 W0 |: c% y4 T2 j
    12 ^7 P( o" }1 P/ k5 N
    2
    & I6 q4 Y6 n& k% }3* @% |4 |, |; u) Q" V' h4 P7 M
    4; N; p7 K& I( O
    50 i9 Z* T% {7 u8 U! v8 t
    66 H5 }2 N8 z& M
    7
    0 E8 R1 G+ F& q5 m3 ]8
    1 ?% O, F' N3 \% E* {Example5:取出5月初至7月15日. p$ T! q  b; q

    $ O  `5 O2 L# m; s+ @s['2020-05':'2020-7-15'].head()
    ( V" W1 e; T& f& r) A) |9 lOut[55]:
    7 F  R, e  n7 }9 M2020-05-01    0
    - L$ E# H  f, g/ v* B/ @3 V* I2020-05-02    15 w) X' c3 @/ K( `% F" }7 f
    2020-05-03    0
    2 f8 e3 \0 s( E6 a! O2020-05-04    1
    . {' b2 w0 A% h! t4 Z) Z, n$ `2020-05-05    1
    3 Z: p9 |- ?5 _1 cFreq: D, dtype: int322 k7 v9 ?2 p5 i9 z* Q/ s9 R
    ! U6 Z. C9 U( M7 ~
    s['2020-05':'2020-7-15'].tail()
    2 n# m1 I) ^+ oOut[56]:
    4 L" O2 N' Y6 _9 e9 z9 ~2020-07-11    0
    ( \" |5 X* u) x# N1 m+ |7 X& r! `2020-07-12    0, d' j3 N' M3 c& v) p" b5 j
    2020-07-13    1
      `/ x5 }8 u; z/ E' u2020-07-14    0
    4 ~  q& u+ M: S+ R9 |* {2020-07-15    1  j" o( o0 W9 V5 C" h& q6 K
    Freq: D, dtype: int32% q/ z4 ?5 s# a$ R, m0 z

    8 d* ]- V& v  y4 g1
    & N6 }. B8 d# f2
    & x2 _$ ]3 Z" _2 v3 c: x+ ]: j3# P- A+ n' s7 I
    4/ h  Z- [6 e( o3 q# m
    5& ~) g4 i8 h" r# d
    6* S; c4 e( E9 \* R6 W
    7
    : P7 ~8 J0 O5 l) R3 Y1 F8
    ) y8 [* U$ X/ p0 a# x9# Z; I7 o2 k: f9 B( z" }8 a
    10! E$ g$ Z" H6 v) Z
    11
    - G/ G$ z; l- b  ]/ q; L12
    ' @- D- N% {  K13
    6 G8 X% @/ }; e5 \+ u+ t14* b& _8 z4 u! }9 y( B+ @* W
    15
    0 H7 y( v8 A( x$ M16
    ; p7 k! _7 u3 X6 B, a8 e/ h. G17
    2 Q" H9 |7 {: ?3 r# r10.3 时间差: Y! J2 J+ h8 U4 G% p2 ?
    10.3.1 Timedelta的生成7 V$ E4 ?, ~5 C6 m6 W
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)) O+ ~) i% b8 @4 R# A# q
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    9 q  k' m2 E$ x* }  可能的值有:* l* e' h3 c6 T. i0 ~% D

    , q$ _3 n" [8 O0 ^+ C‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    " g: e6 a* l: q$ g‘days’ or ‘day’% ~4 @$ V) b) R2 L) A
    ‘hours’, ‘hour’, ‘hr’, or ‘h’
    3 m. V2 _: `+ a4 ?9 q‘minutes’, ‘minute’, ‘min’, or ‘m’# _# H# g) P7 D: y$ h
    ‘seconds’, ‘second’, or ‘sec’5 r& O- M" h0 `$ Y6 ]% Y
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    ( v( o% z, j; a, {: o+ S微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’5 Q2 I$ J( B2 Y
    纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    " c2 }- L; B+ `7 Q* ]0 w4 n. ]5 Q时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:" Z- u# q" [5 h- B! p
    pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    & b5 A4 T0 w1 u( jOut[57]: Timedelta('1 days 00:25:00'); C0 ?3 Z9 N; \3 ~( D
    , X- A3 a  B4 t- L3 \' d, g
    pd.Timedelta(days=1, minutes=25) # 需要注意加s4 `0 o( l' ?1 D- n+ j0 @
    Out[58]: Timedelta('1 days 00:25:00')
    1 x* i, g  G  c
    1 o8 e. t. p9 kpd.Timedelta('1 days 25 minutes') # 字符串生成# v' t" ~/ G+ d. i- \
    Out[59]: Timedelta('1 days 00:25:00')  d6 B, c, |4 X9 x% X

    ) O$ J3 g! S" h2 opd.Timedelta(1, "d")
    + I4 l: B- \; K. NOut[58]: Timedelta('1 days 00:00:00')
    4 F3 r! Q3 |4 e3 S9 Z# h1+ c- k4 d% u# K* M/ i2 y  b: Y0 `0 q
    23 X  S6 ?+ M, ]+ M, Q
    3
    # T' g8 x' f) d4/ N/ w& R* b8 O% H/ H; p
    5
    # z  _: h# L- Q6. e1 X, [1 i% t2 H
    7: Q( z/ P; V' p2 R! u7 N' m3 |
    8; ?/ g  ?) e6 W) }2 V
    9
    - X' j& ]% U) \! U5 t3 ?6 {0 T( _" t10
      g, @& ]9 B; H1 v11
    2 T) d% D9 F9 h) L. l' m生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :  ^4 I% i) u5 }
    s = pd.to_timedelta(df.Time_Record)
    7 N9 R& f$ {* _6 Q: c3 w% ^: y
    0 Z; V# z5 l( ]) Ys.head()5 d; g# v) e8 B& h  p  p
    Out[61]:
    " ?% Q, Q7 k. ~: q  k4 S3 V0   0 days 00:04:342 t8 s6 B; D' P* u" e
    1   0 days 00:04:202 E1 b4 G) n0 R% b5 E; X
    2   0 days 00:05:22
    / |( A7 h! b! G' M3   0 days 00:04:08/ k5 N% p: R$ \6 P% g# n
    4   0 days 00:05:22
    5 I8 b. Z1 z, RName: Time_Record, dtype: timedelta64[ns]
    & ~, E2 T( ]( H% t' d& g( T* m1
    % M$ |' F! y. I& X: `5 R; O2
    7 [% K) `4 n2 \3
    - H5 k. @% p# ^* u, q4
    " r) ?% l# N+ \+ ^1 W51 y* c  p  `- J, L8 G4 x2 }4 _9 e6 T
    6
    - p! d; t/ L; L( f7
    # f( H: ~* ~6 V- M* P, j" j8
    8 m8 U0 k! f. A5 d9
    # t! t' D" y, f4 r' N10
    1 _2 p5 H' W; Z# Y* _, e与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    3 P% M) D5 Y- s  \& {$ x6 X4 ^pd.timedelta_range('0s', '1000s', freq='6min')0 v% j# e1 b2 g( {* R
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    ( ?; Z* v& w$ C1 L* }% B, v9 `: k& |
    3 f* N1 l4 i& H) H7 spd.timedelta_range('0s', '1000s', periods=3)9 |+ x* O. o7 i( }5 x6 M
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)# _: o' z: P, B. t8 G
    1
    . }" w6 h! N1 {' w2
    $ L. V2 F" m  l* h38 U# q! w7 a) r. M8 y
    4
    : `1 M. C: o4 `5
    8 {1 t+ O. K4 R1 l: k对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    2 X4 T% ]" x3 ks.dt.seconds.head()
    ! x, I* I3 V# Q# J+ y0 g3 zOut[64]:
    % X6 @* N) s- N- {, Q0    274
    6 K8 l8 L2 D" y! F4 E/ t- \1    2600 i/ V/ M+ q4 I# Q0 ]
    2    322" s5 z. A1 a2 A& n
    3    248! N% ]4 v$ ^2 X" Z5 G8 {0 j: [
    4    322
    ; K0 v( m: I( j3 e0 Q2 Q: J1 \% nName: Time_Record, dtype: int64
    7 @2 k$ q* x7 m, D9 O: H* E) p1' D  a1 v$ h" o1 G( @( ?0 v
    2: u2 K3 N, F& A- ]8 \- D
    3$ c/ c1 ]0 a. n% J7 k
    4
    5 P' ~4 Q2 g" w0 c5" W% |0 C4 ]9 \
    6
    - v5 Y0 u, [$ k2 z! T7$ a1 N. v2 d& R
    8
    + O  x6 X7 b) w! M5 `$ G  h! {如果不想对天数取余而直接对应秒数,可以使用total_seconds
    9 l) K' `) Q# P* K4 K4 `$ B
    + }% L/ j/ G6 l1 Hs.dt.total_seconds().head()
    9 T5 r) ]5 b+ ?2 K  t. k0 @Out[65]:
    ( h5 Y/ V- ]/ s( B% @; g0    274.0
      Q6 p+ ]: S- n1 E' k7 y1    260.0- u6 t  W* {: ?/ A8 L) e
    2    322.0% b/ Q0 L" O% v
    3    248.0% {0 e* u" {) s1 g3 t1 q0 m) V
    4    322.0
    1 T% Y' i/ |$ o0 B! L- B% cName: Time_Record, dtype: float64, U6 l9 z: n- p" x" ?- d) M* W& `
    1
    + t$ y- O5 f, }6 j2. X/ K4 }: i5 B
    3& L4 M# N" j! N* p( @
    4
    3 a+ @  B$ c7 ]5: [3 U6 x, m/ B) q1 @% w: f
    6! X$ w* N' w0 n  M3 Q1 u
    74 E! o. N  R3 F( j: H) ]
    80 Q+ G0 G- i0 ]/ U( I- `6 [
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:0 K0 n! E+ O3 V. i0 t

    1 ?$ r7 @% L8 P+ \pd.to_timedelta(df.Time_Record).dt.round('min').head()
    & u- c' R5 I- M6 K1 L% tOut[66]: 3 m/ \, o: m  V9 e  X; Z& @
    0   0 days 00:05:00% A: Z, h' b: ^" D, t2 z
    1   0 days 00:04:00' M# ~" U. m. t( w0 ?; V; m( F
    2   0 days 00:05:00
    , e. ]0 {  Y- {' S* b% c: ?6 W: J7 x3   0 days 00:04:00
    5 c* H! j+ Q; h4   0 days 00:05:00# U: ?( F: X; Z5 z2 }
    Name: Time_Record, dtype: timedelta64[ns]& F/ C) h7 k! M) \+ y
    1, \! u1 o8 f8 c$ D( K; t  I
    2& e+ K9 J5 R. G, c# _6 ?9 S$ G
    3
    & ~, V6 b7 K# Z8 z3 Y. k4
    6 d1 j' H  Y9 |% _, v4 G5
    + Y* f- f/ _0 f( F/ N0 f6& ^; I$ Y, N* q, b$ L% [9 |
    7
    & z! Z+ H$ U) y( d; r* l8 W8# I+ r/ |$ j5 {% e1 P8 R/ G2 k) G
    10.2.2 Timedelta的运算
    % E) B( X8 E  v. K' A2 y$ R单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:8 x& h: \  X- M
    td1 = pd.Timedelta(days=1)5 A4 m0 r. n$ c4 }. h1 O9 m
    td2 = pd.Timedelta(days=3)
    : e6 S: u9 b  P  _" K  jts = pd.Timestamp('20200101')
    4 E- s  d8 z& k% x6 n8 V" D/ a' L+ f: F
    td1 * 2
    ) O: H2 b5 B! A( W* y& oOut[70]: Timedelta('2 days 00:00:00')# }3 G' Y! w, B" h) a+ h- J" H, V9 @

    * {# `: }& o% K$ [* q" {- _/ Utd2 - td1/ m# s' q' O5 Z2 x0 k: u
    Out[71]: Timedelta('2 days 00:00:00')8 F  Z2 V  Y, B: H5 T

    ! O, X& V+ x0 k/ its + td1
    " G  k8 C8 R! X5 Y/ KOut[72]: Timestamp('2020-01-02 00:00:00')( }; B2 w9 ]- n; \$ S* m$ R
    + B' h1 u& a9 ~+ u
    ts - td1# T! f) x4 O+ F: `) F: z: P
    Out[73]: Timestamp('2019-12-31 00:00:00')# l+ r+ K& V* R8 R) `9 e3 l
    1+ t1 Y* T9 d" ]( @6 _+ }9 I
    2
    ! Z5 h+ T2 D( p0 E: F3& \5 j5 v0 S! }6 X# K6 P
    4( n2 h5 ^2 T0 g8 M9 {  f
    5
    " M8 M. U; l. L; B) g6' t! G/ S2 x/ q
    7  x; Q2 h/ ]3 p& w" l+ @+ `
    8
    + {8 ~/ v+ `" A) Z2 f90 ?; q4 |' Q+ ?0 K
    104 A" ~- U, i% B0 ]+ P0 V
    11
    # j& b, u% D3 j! `& P# y( V; q+ D' M12
    ( k6 }9 e/ R1 ?13: V. Y, p  l- m$ ^- V. \
    147 x. [: ]. h) U$ B$ E8 R3 ?
    150 x$ L4 K: E/ z' |9 @
    时间差的序列的运算,和上面方法相同:: ]2 j/ F, f6 K$ B- W3 h( G% c# }
    td1 = pd.timedelta_range(start='1 days', periods=5)  w- P# x9 h, [
    td2 = pd.timedelta_range(start='12 hours',8 @2 h6 E" g( V6 d8 P
                             freq='2H',
    : k% F- r! [) k' v5 j8 k9 ]                         periods=5)
    ' T- s! C/ @( y7 o- jts = pd.date_range('20200101', '20200105')
    ; C8 D& T8 k, k( F. Btd1,td2,ts2 ^4 K3 b7 A$ L
    2 `5 j0 [1 b( Z; y# |$ j& Z
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')) @/ B7 v. _& M  n; k4 G' [
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',7 h4 ?) S0 `" x
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')! a9 l4 l! D, K* [5 Y% x9 M
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    9 O- E( t$ ^4 Y, G6 }( h               '2020-01-05'],, P3 K4 Y  @8 W+ n
                  dtype='datetime64[ns]', freq='D')
    ; h6 E/ R( w" |, l0 Q; N5 |1$ Y0 `& V5 W" x! t. Z, o2 g& x  d
    2
    4 e- E3 ^% o% r0 p$ U" [3
    3 c- m& }+ d1 N9 c$ Q45 H) ?) w. }$ l7 r
    5  G7 v* n+ n& t2 L" X. D
    6- u5 ]6 s. `) v1 J. m
    7
    % p6 J1 G* E# {$ `* [86 F* s, E1 f0 V* Q& y
    9* x! |$ S6 I- ?$ {, `0 n
    10! U( m+ r/ l, ]7 Q- n
    11
    3 C+ K6 }/ t1 y/ `+ \. [0 q; i12" B" F0 }6 H/ t; r. p: Z! [
    13
    ; e2 ?3 y- M; v) o- r" ktd1 * 5, c3 D( e1 Y1 C) L$ i' @
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')5 K, T; u# u- y& \$ u

    ! h5 ]5 E" m( m! x1 B6 ]td1 * pd.Series(list(range(5))) # 逐个相乘
    . l% r3 ?5 S+ K. a& Z; gOut[78]: ) e. k/ O  ?! n7 w! B6 B
    0    0 days) B) [- v+ s4 W. k# e
    1    2 days
    8 B( h- _# C$ p) W2    6 days
    4 B3 K  m* c, u/ E& R' J3 z3   12 days* x" @' o& f! j
    4   20 days  q! y9 L2 Z7 j
    dtype: timedelta64[ns]5 V: u/ y: D- G+ L& Z3 B2 G, k! f; r
    ; l( F0 h! t/ g) ~
    td1 - td2+ @( f* h& r: |: j) S9 \1 S, F% J
    Out[79]: 0 L- }6 v/ L! v1 l5 P1 D2 {
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    + p" M3 B7 f- M. {) X- @* O- r                '3 days 06:00:00', '4 days 04:00:00'],
    + K7 O# W2 J. a5 {               dtype='timedelta64[ns]', freq=None)
    6 B% |, f; Q% `0 y2 ~8 h& {- m6 ~+ i$ C3 c. z5 }9 n
    td1 + pd.Timestamp('20200101')
    9 H# B6 s" e' z5 c/ a' a1 S! D' \Out[80]: ; n# v2 T# O# r. V' R
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',; k. Q$ q3 t* q/ P: N/ {! s
                   '2020-01-06'],dtype='datetime64[ns]', freq='D')) g* d) u2 B) m$ d% t% h' n5 b: W/ A
    9 b$ D  G* O3 v* Q& A% e( W
    td1 + ts # 逐个相加
    4 V$ {. ]! H7 }7 \$ I0 WOut[81]: 4 N% T; |0 K" c6 t4 T$ M
    DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',  d9 x, y" |1 ?% n$ y( F
                   '2020-01-10'],; D; T! B8 Y5 P: R2 {+ H
                  dtype='datetime64[ns]', freq=None)) |- e/ E2 z& Q
    0 _) N6 V* M0 G$ p, \9 X
    1! z( [3 ~) q0 X/ T( h! y
    2
    " A5 ~% u. ]3 ]38 J4 \8 |, Z& u  p
    4& T: n1 ]2 S7 A& H/ ?# u% B3 }' D
    57 D2 ~+ z/ Y6 D) P+ n- g
    6( _4 @9 c  u; x5 C
    79 }1 a* D. R' a2 V
    8
    + L0 j  F0 ^% t  i# N2 A9
    0 c' P1 R3 A2 ?% P3 ?% n. n10
    * V. e6 }9 I$ I11
    ! U' P3 o# j9 n2 Z12
    $ b$ A, X+ L) K* F* p/ X+ c( X& ~13
    6 o2 s3 e+ f9 W14- }/ w7 H5 h* ]% E9 I$ q! R& C: U
    15
    # i6 z% P4 R" ^0 G, n( e16
    3 s  s9 R% C  h! C$ w; @17
    5 n" H# d  j( s' a18
    5 m) J9 y1 D3 s( Y3 n19+ R: x; Q7 R* V" Q! E2 K' w
    204 k1 ]8 ~5 Z6 H8 F2 g7 o
    21
    . H. ~5 v: E8 g7 \22
    * u! u* Q5 N  }& g3 |* ~23! k' ?, d2 d& H" I$ ]) q! ]; ^4 Y
    24$ x3 E9 I# ?) T0 L3 o
    25
    + k/ J5 K$ J2 l6 Q% s8 {; }26( S/ V, |0 P5 f& P9 P* Q" x
    279 \3 u& |- I" R  @0 k" }- n& ~1 q
    28* f( p3 A3 i, f) G8 g
    10.4 日期偏置) b& f3 [& h7 N5 d$ Z  _7 V
    10.4.1 Offset对象. {# E  G( e: \. d7 n
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。3 Q1 y; B5 l7 T: B5 ~8 o* V

    , k) V% ?8 Y) c% {DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:1 h+ C& w& C; h! U, D- z

    9 O* o4 U! ~8 K6 H/ h6 }: {& Q5 T/ Xs.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    - V. ^: n( s, R$ ~s.kwds:{‘week’: 0, ‘weekday’: 0}2 b5 O! F% Q+ ^; u: C* G8 p! \6 T& H
    s.wek/s.weekday:顾名思义5 f+ k% X) d# T
    有14个方法,包括:
    2 V* d# l+ c! W! D0 |
    " E8 l/ z- }" S0 m8 mDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。% l% j. [- @( M% W
    pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
    9 Z- f' ?% W# m5 Z6 e0 I5 S  }, k" |: ~+ k2 R
    有两个参数:
    * @' T  M- Z) B. u* t: j$ Vweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
    ) Q: }5 ]9 s0 j( I$ _5 Q! Oweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)7 }3 G, G% H8 n$ |1 G1 I
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    % H+ f) {$ H' [6 E* F) W2 [$ z0 K# @2 E. d5 f; j( E1 [
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)1 }5 T: I( y) Y" z# F
    Out[82]: Timestamp('2020-09-07 00:00:00')
    ! T& F2 g+ X9 Z6 z8 T! X; ~* |' p/ i3 ?5 ?
    pd.Timestamp('20200907') + pd.offsets.BDay(30)
    6 c) ?- G8 Y$ |/ DOut[83]: Timestamp('2020-10-19 00:00:00')
    ) o* l% w$ |( _) d' b* n' z2 m5 @1
    7 T1 v' M0 k, P1 F2
    * z, H4 T' J7 b- Z# }7 p" k3
    ; H  ?4 q/ b2 @2 u6 P" Q- X* N4 Q" S+ H4 L4
    " C" B. U7 Y2 _/ G" N5
    * B: _3 E4 C, B. r+ m! q  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:5 \) N5 [5 q! D- ~0 E6 W

    ; [2 J+ Z/ c8 M" [& g3 ~. jpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    4 I) T3 L2 N; v- g) EOut[84]: Timestamp('2020-08-03 00:00:00')
    6 J  g7 q7 K1 ~
    7 {/ c# S* Q. D( Q9 V# u* Mpd.Timestamp('20200907') - pd.offsets.BDay(30)7 `; v. ]+ f% s- V3 M8 k
    Out[85]: Timestamp('2020-07-27 00:00:00')
    " Y" q4 _* P+ V) W9 P
    4 R- P% ?7 N( i3 |2 S8 k" `+ O' Vpd.Timestamp('20200907') + pd.offsets.MonthEnd()$ ]1 H" W8 n! L7 f8 g
    Out[86]: Timestamp('2020-09-30 00:00:00')& `5 l8 J- u* Z" m& N/ y
    1
    / n, c- H) b9 v4 k5 `23 L! {" |8 ]: L7 i4 q% Q6 w5 m/ g
    3
    . R. b+ v1 S# J# j7 F& i4  D1 p2 f& W) U  u1 q1 D
    53 k0 F% d( L3 _. @* Y
    6) t2 j9 u+ r3 F' G( z
    7) o4 H# ^* i# l; x/ ]. L
    8# r) [. t! h8 s$ i6 Q9 V' M
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。' ^! l6 L$ d$ H
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:/ R1 W7 b1 t8 D; R, |7 Y, g
    ; k% S& o% R4 o+ q1 x' _
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])! |9 p! U# H' G8 T/ g
    dr = pd.date_range('20200108', '20200111')
    " k- o% }  T& N  N
    # N$ M5 A# q, p1 b; `7 b# Vdr.to_series().dt.dayofweek) L$ `# R$ x$ M
    Out[89]: 5 o6 h/ u* |: P( }$ n% i
    2020-01-08    2
    ( V8 o  l+ q9 M* h; N2020-01-09    3% J' r1 v# V  a! j+ Q, _3 ^
    2020-01-10    4' @2 L. P* N; \% a* l
    2020-01-11    5
    3 Q; Y6 @  a. g5 Q8 q4 DFreq: D, dtype: int64
    ! G& ~% A( o5 V, W0 V! {* N" [. ~% E5 O) F. F, w3 B  K
    [i + my_filter for i in dr]8 x5 B+ G5 _: k$ i& p0 H
    Out[90]: 6 b- o% m* R* g: q9 ?- ~
    [Timestamp('2020-01-10 00:00:00'),2 B, H. |8 K* a
    Timestamp('2020-01-10 00:00:00'),8 S& x7 m" B" X! N/ l
    Timestamp('2020-01-15 00:00:00'),
    0 i6 C- D0 [2 i/ y: p8 p2 g Timestamp('2020-01-15 00:00:00')]
      s+ Z  G$ c* J  h- Q
    6 f# z/ k% Z4 P" h. _$ i1
    4 k! L6 a) \$ \5 A2- W2 A( |" n  m
    3' W4 S/ ]+ f4 S
    4
    ) R* m6 U' @9 R5
    8 j: W/ o; h- p7 R' l5 I6
    ' E  z1 Q+ X/ A; l! s  m) f7$ Z9 N9 ]8 j5 ~0 Z$ g( @
    8
      g6 j4 U5 Y0 t: X8 b6 U! [9
    4 `0 Z6 |9 c; I10
    - ?/ A+ O4 g2 i1 C* ]% \2 N, `115 t  q& `( e% E% l. V; N: Y
    120 k/ j* s2 p1 _8 u/ F
    131 C3 V& B, F, U% K- Z4 q
    14
    4 l1 }  _' r. ~2 v  O3 e! O15  K3 _/ ]" t$ z+ Y5 h. I# \
    16
    * O( c/ a' u6 Z& _5 I6 x0 y17
    1 i( {+ x# M6 y8 M. M( j  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
    1 Y2 I2 `% a9 ]9 Q+ f5 K# M8 a: E3 A, X5 ^5 o- F
    【CAUTION】不要使用部分Offset6 f, r9 }  P6 P' d0 v
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。' d# E) Z& ]7 m" z

    . j$ L/ \" l( p3 T" T. l10.4.2 偏置字符串
    ) e* J: s8 P; q  c, l5 c  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。8 e- ]; u! K- g* S! Q

    1 c1 o' R* E# t) L( ~0 l+ t1 V  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。/ x: s: n0 r# a4 T5 R- w

    ) y1 _* |) x9 f" v; D9 opd.date_range('20200101','20200331', freq='MS') # 月初; E7 `: h+ \- B8 C) R6 W
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'). V; Y- d4 q/ g% A) p4 F) P

    & h4 V/ _3 {9 Q: G5 g4 ypd.date_range('20200101','20200331', freq='M') # 月末
    4 c) Q/ }8 e4 b  }" k. T7 |) cOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')3 ~; ?+ Q4 f: s5 n+ A* M: A" @
    # f/ }( ~; P9 F5 H) n
    pd.date_range('20200101','20200110', freq='B') # 工作日$ }# k" L* X8 Y/ Y2 b
    Out[93]:
    1 H# G5 A5 @1 Q" O) Q" UDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    . {% d* y5 E3 I; t               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    ; O8 k& o- E" o0 P2 O8 l4 k9 s              dtype='datetime64[ns]', freq='B'), i7 X* j7 h$ |) ]: Q; y% |
    - |8 p8 p3 W( X) u  i
    pd.date_range('20200101','20200201', freq='W-MON') # 周一: \. @- Z0 ~( \% H. d
    Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    ' K1 o3 |3 I+ B1 k6 n5 ?- G3 l/ Q: D1 |3 g6 T
    pd.date_range('20200101','20200201',7 J  y% b1 y4 \) B/ T8 Q9 l: L( W
                  freq='WOM-1MON') # 每月第一个周一- C8 ~- g: [  i% e3 q
    ( r" [# P; m& o" n- p% n$ O5 b
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'); v% W: O  Y* x+ x& c) H
    * t7 L5 I2 ]" }6 L- G: x
    1
    ! E. i) D$ A: E( Q' o- }2
    3 K4 w1 B/ _/ Z" R2 j- n' x# w3
    / n0 y- _/ [' q% f: b+ P0 `4+ z# G1 |8 U( h# q( E
    5' ~! F. L; j! M) X) R/ `6 ]
    6
    5 E5 r  {5 A3 B0 }: [7
    ; `  i3 u5 E; L, x7 X8$ g1 R4 p- d$ }2 X6 _. m" ?# i
    9
    ) U2 f& G2 J3 V4 d# b10
    ( s+ [% j& \3 k: E  N11
    " C- U, ], ?+ m6 S; _( f3 g# G12
    3 u+ G$ y/ [  U9 f3 C7 D13
    3 E* D6 n# Q, O7 w$ j, d( a+ o14. p  T$ ~* i3 A1 s7 Q3 [
    15
    7 M% k2 M) M) r- J16( Y, n" a) Q8 _& N2 y
    17
    7 Z+ k$ Y5 d& ]; U, k18" K- c4 y5 j! W+ m/ l
    19# W. L/ \. h+ G0 `2 `
    上面的这些字符串,等价于使用如下的 Offset 对象:
    * Z6 g4 Z9 v7 f7 O( p, W+ p' n  Z2 L! h
    pd.date_range('20200101','20200331',; j8 h& Y5 C4 \9 A+ @) x
                  freq=pd.offsets.MonthBegin())0 l8 H7 p! D5 T: c4 P; t

    " K& i6 P9 W/ o: T% Z" f4 M# ?Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
      j% ~+ I* i6 G& a) ^. o- Y7 U, |( L& t% Y# v
    pd.date_range('20200101','20200331',' _, q  T; L* ^9 Q5 D3 U3 v
                  freq=pd.offsets.MonthEnd())7 J, B1 S1 U. [

    # e: W3 |" c7 W1 b$ f( iOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')9 `* p9 N1 Q7 A8 W

    8 h  [& n$ w7 l% k3 g% @: Ppd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    ) ]0 p1 |* }& @1 hOut[98]:
      P$ m5 c: L9 b5 yDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    0 \0 X! T+ w* A3 A, d" D, E               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    2 |0 t* v* Y; k( {" M              dtype='datetime64[ns]', freq='B')
    1 r+ ]3 d% _- c- q" M
    " q1 N1 ?" I  l. J6 |pd.date_range('20200101','20200201',
    6 ?: U1 R$ x1 ^+ }              freq=pd.offsets.CDay(weekmask='Mon'))
    ) w* m+ N9 ]5 V3 Y, c
    ; v, E: u8 r0 Q- NOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    3 W; W- x- H2 x, w: O, m8 E0 E0 E
    pd.date_range('20200101','20200201',: L" C9 E* i8 ?- P
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    6 b  y0 |5 W) V( Y- a# \& ~% N; L
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')+ l/ ?& ?' s# L: D! T
    4 v* L0 F- a4 g2 S6 ]5 U2 A& D
    1
    + @2 Q# q7 z) }/ B* `+ R/ A2" N; C+ }/ v  f6 ^5 Z5 p
    3
      s5 T# N% [) ^9 @: F4& N9 h1 Q+ n: _0 k6 ?
    5
    4 F. s: q6 o$ |. C+ M) V7 I6+ ]  U; c2 [) q7 g% _
    7+ {7 {! c7 T! K) Z! H0 I
    8
      C) M4 Z$ E$ P) j" Y; d9+ T% P+ U9 Q% ?* j6 i, t
    10# X4 J1 ]0 ?( N" k* }, L
    11
    6 v; z( e1 O) C12. |. @3 Z( y' |4 }6 |9 a
    13* n! e5 ~: z1 [) n
    14
    * ]4 s. u6 P# W15
    ; f9 k7 p0 j, W! Z! H160 t: ~8 I, E) o$ d1 Q$ ~
    17
    8 P5 P7 u6 J% N% l18( E+ [( C6 _1 A3 p+ E+ v6 @+ z5 y
    197 O: Z4 ?- L" S  ^& S1 u% M* G  z
    20
    3 v/ T6 w7 S& }  W2 T) c: p- h21
    $ U( d1 `; b9 l) h& }6 |) E+ ?. @22* D* B- p7 r4 e+ T7 W+ e9 a' a
    23
    7 ?% z+ D& T8 Z' R24% o8 R1 x* Y: {& _. C
    25
    6 a3 ~& T7 y  m4 P7 T/ h【CAUTION】关于时区问题的说明
    ' o% n% V( X$ Q' I+ D9 O  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。. J& A. N1 F% t, u, l/ B- P

    , r( B( I$ @% ]# @10.5、时序中的滑窗与分组
    - ^% v0 W  H) K3 v8 v10.5.1 滑动窗口
    * g+ X$ T0 K2 [; }0 @  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:4 h8 _) q" O- a

    % ?: g  D! Z+ b4 `! uimport matplotlib.pyplot as plt
    2 \0 h# i& q$ a' Y/ e9 I& _idx = pd.date_range('20200101', '20201231', freq='B')/ G  t* V: F, h& d6 v
    np.random.seed(2020); f3 J9 n8 D8 w$ b; Q

    * R! [0 g. M/ e  i! w5 U8 ydata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
    ' p2 Q* @- E0 Y; [% X5 N6 M! vs = pd.Series(data,index=idx)
    2 G* k# ~- }* U7 w  X3 ls.head()0 X! ], v$ S+ f3 K- C0 q
    Out[106]:
    - _. U4 |% y( z0 p5 W  S2020-01-01   -1/ Y2 i( z9 d$ W* B
    2020-01-02   -2+ G' ?3 J  s" s$ \
    2020-01-03   -1
    1 y8 l; L' T9 Z" @& Y, m2020-01-06   -1
    2 O! u% N, O+ ~, j. p2020-01-07   -24 @, E- f' B, {
    Freq: B, dtype: int32
    ) a( E8 o1 ?9 B. l7 i) L6 s+ xr = s.rolling('30D')# rolling可以指定freq或者offset对象) E/ S0 q( A4 j8 }6 o
      p3 I. F* ?1 U. X6 y! g
    plt.plot(s) # 蓝色线
    $ L! ]( E- l- T0 [. iOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]  Q2 m8 W2 A5 k; y* p  x5 k5 j' N2 Q
    plt.title('BOLL LINES')
    2 \/ O8 R. ?8 T1 r& N' F( ?7 cOut[109]: Text(0.5, 1.0, 'BOLL LINES')2 a6 s2 a  ]; Q+ k' U
    ) `* v* x! @1 v/ ^
    plt.plot(r.mean()) #橙色线
    . t7 R6 I* t- n3 P1 XOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    + i8 B- y" n% B+ [4 I# j- J" R( s. |' m# p0 v
    plt.plot(r.mean()+r.std()*2) # 绿色线
    2 S* }- P5 L+ h- a5 pOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]- G/ I8 S5 F, ^. B2 G3 ^
    0 D3 T9 I; @- Y2 t& P& I8 M  v6 W$ T$ Q
    plt.plot(r.mean()-r.std()*2) # 红色线
    0 R% Z3 W, f/ Z/ `+ ?Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    % K, F& L/ f5 Y3 X8 Q  \4 z. _/ A9 R* C9 F
    1
    1 e/ S# T3 j2 t. X2( H  T; [; ^* y
    3
    % l# m1 w) b. u  h6 Z+ n4# u% s- Y/ j5 }( Z3 g- _
    5
    % A: ?; h4 s4 b- m69 ~1 |1 D. j& ?- |. s: U* r5 w
    7
    8 U: N& j# s( d: U& A89 m6 ]. {7 A1 p, A( {
    9/ f# U+ C; [! W: ?4 n( _
    10
    : \2 r, W: \" d6 m11& ~  q1 p5 R7 `: I4 P9 K
    12! d6 A5 y4 u  M# U# E2 e
    13
    4 |" h8 H$ F& V, t# Q: G% I14
    9 A/ ~% X& u6 e. }. Z. h' ]15
    & R1 P; ?3 ?1 D( g1 Z16
    # ~% x/ e! Z% \! }' z" S17
    ) y% Z- e( q9 M% j. Z+ g18
    2 h1 O3 @8 b- ]: i9 k19
    : b6 E* n+ H/ Y3 m8 W% C204 W  d8 X, K9 `+ _( g" }) T2 Q% p9 r
    21! E; b$ {) ?; |- ]% v+ J" m" G5 R
    22
    5 n% y( D* M! G) P7 \236 b: U8 d  V  R5 z3 x( n6 C
    24
    & L, k; @3 ]+ V& q% A! O0 c+ O252 ^) T: Q2 i7 g! @
    26: ^* F/ a* K7 T: n
    273 H" R/ A7 G- i! F6 C
    28
    * [' V5 R% _7 Z29+ @2 ^# N4 R' E

    ( |. T/ z7 w: U% M   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    $ C( p* d/ V  X) y3 t   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    ; N0 }  I2 w& G  n; y3 C4 B) ^; p5 S8 k: V6 `' y* z0 q
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]4 P$ O3 H# q8 v8 ]
    bday_sum=select_bday.rolling(7,min_periods=1).sum()) Q; }, ?, N- N6 {
    result=bday_sum.reindex().ffill()
    * G  l) u+ a1 \5 bresult
    + [8 T) P! j0 z6 j, c; {" P, [/ `9 f6 a6 v+ C! N
    2020-01-01     -1.0
    / h7 V/ M$ [0 Y8 i, L; F9 B1 d2020-01-02     -3.0
    : q7 h4 n* `5 X0 b( `6 z2020-01-03     -4.08 ?7 W4 B& w9 w
    2020-01-06     -5.0
    6 y5 L0 H& y: A2020-01-07     -7.0
    0 R8 u6 T' R: E# [              ...  ' B8 {3 f' j) w) l
    2020-12-25    136.0
    & ^; Q! ^9 E4 c2020-12-28    133.0. y' o1 [9 ^# ^& t# y1 a
    2020-12-29    131.01 E1 t9 Q1 x2 V, A0 u9 a$ _
    2020-12-30    130.0
    9 S/ D. Q5 i6 q$ @; Y2020-12-31    128.0( Y+ d6 D/ m, a& S4 b
    Freq: B, Length: 262, dtype: float64
    " F8 B! c: p( z! w4 o
    - E6 ]1 k  N) E' z1
    ) b0 P( u/ Z# i, E  c24 n! P- G5 o2 M% `2 j
    3
    . P6 e; U, c# B4
    0 \4 E, `( k7 g2 ~5 ?( e5
    0 X8 p" d7 P. V) |6 ~6  F4 O/ E8 K3 k9 e: r
    7* r5 j# M$ f% s. r! W% D- e
    8$ S% j$ r& l) ~7 e& {
    9
    $ i1 \0 s* B5 A! O5 b  W3 \! o' p: J107 h" r$ q- m4 b! `
    11
    % d6 S: J9 N* t/ H$ u12
    + j; E% T+ ~8 L/ i) F/ K138 }1 L2 u3 s& X" D0 e& X
    14
    ( t1 ]2 o5 y  e. J3 X15
    ) u/ v, j* t- k, q16
    3 w& K. L. e! w* H179 q  d; Y- p/ i  b( ^! _9 S
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。5 X0 Z! e; Y# G# r

    8 @; n5 Y3 H1 V) p5 h  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    1 C2 u4 m& k) ~" `+ e" y
    ; T8 u/ x/ m7 h# D* Ss.shift(freq='50D').head()
    , `) x3 J3 K" IOut[113]:
    * a6 ?2 d$ h$ U0 S* r2020-02-20   -1  Q' {8 b9 f6 I# `+ q! z/ l3 K
    2020-02-21   -2
    ' K3 r7 H, G; |. ^* h2 G2 t2020-02-22   -1( y' d* O- ?* U& h8 [
    2020-02-25   -1# Q% |+ V* `9 ]) ]1 Q% k% c
    2020-02-26   -2
    ) S* C; q- J$ K% v% kdtype: int32
    ' o1 T" R+ H, L: R/ D1+ C* R- I) @- O$ N; |/ L9 O
    2
    8 R& i$ f6 h: P7 B35 d7 d; ~; Z3 Q8 |; r: z4 V
    4, Y* _* [1 y, N8 N8 x$ c4 a  X- w
    5! U1 s( ~5 [. U! }# L# J. @. O
    6& F- ?& m( l  p, \+ i' R
    7! h3 h% y9 P2 y% n: Q; ?
    8
    $ J7 x- x. o- v7 c  \" C  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    ; p, X! v2 ~- u1 h  s( G0 h4 S6 \; N$ U
    my_series = pd.Series(s.index)0 n& I# V' w; y
    my_series.head()
    ; o' t3 d% z# Z0 e, [Out[115]: ( h% s* O+ }; w' U3 e9 N+ X% s4 U, s
    0   2020-01-012 _4 O( f$ o7 {7 \# o6 [! U% ~
    1   2020-01-02
      @7 z/ e. e% j9 O0 N3 q8 F2   2020-01-03/ I6 [; ^/ j+ x% U& D
    3   2020-01-063 U+ G4 t3 r0 `( t4 Z- f
    4   2020-01-07% m; L0 s9 w# B. G  q4 L  O
    dtype: datetime64[ns]
    5 X5 t+ G% t. n- ?# T" `! Y1 j1 h+ I) c1 |& `! u
    my_series.diff(1).head()7 D  p4 H$ M# I  Q
    Out[116]:
    ; i. j. p% ~2 n+ k0      NaT
    & s' \3 \: }4 I3 c& S5 q6 F" m1   1 days" [2 y; b  C; m* Y
    2   1 days& O. E6 N' {' [* N4 G0 C
    3   3 days6 M6 y: r& M0 r& d% v, C
    4   1 days
    9 n3 S3 B. t' f2 h* b! H" Mdtype: timedelta64[ns]8 U2 V1 d+ ^" ]1 q3 i
    & Z! [0 ?$ ^) Y+ P  O% {4 Y
    1
    ' j- n8 Z3 D% I( W% T; g* L; F! B# w2/ C+ t# ?/ v$ b# }
    39 K( Y& {( j5 w$ x  ~2 H
    4) m% g9 m# ^$ v5 f( S- f; @. T; Z1 q% M
    5% m3 F0 {! E+ N/ [% u9 v
    6
    ; w( {4 K- p) @( m7 N2 _7
    ; l( @. G; \% D' u) z$ J8
    - |* z, @! z; r; z: C9) |0 S8 H/ f. K2 |  }
    10
    7 g& i+ F! s$ w. [! |: `11( d& c6 h0 [9 y  k$ y
    12; F% b; [3 c3 i, e( m  d! ?0 h5 s# @
    13/ d7 Y" S2 D" d4 l
    14
    7 h2 R5 q7 x1 F15
    $ H9 b" [+ l, H7 o; g16) y% r" s: [$ ~' Q; T' }+ C
    173 [$ S8 g4 e5 g: u/ e6 i
    182 @3 I; l2 V% ~0 {9 ^- x: h
    10.5.2 重采样: D0 j; b" x1 k5 ?
      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)  ~) [- W  s' G' f( l, Z. d3 I
    常用参数有:
    % c' f. T7 I' g: D
    0 k, q) a( i$ Crule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    3 b0 {/ R' |" O5 kaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样- f/ s; Q2 ], b8 q/ _! e, u4 c
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    9 ^2 Z) q( G& E5 Llabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    * O* z& R% F& _& i" ?  F7 Jconvention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    ' s8 C( d$ T  P" {' w" W9 Pon:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。) G8 {* d6 L" f8 R
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。- c* _2 s" X  q8 Q4 b$ S; Y
    origin参数有5种取值:
    # a6 q! i. ^: [* b: ]5 H! R+ y6 q# t4 d‘epoch’:从 1970-01-01开始算起, h( N* h) Q% L' O3 j
    ‘start’:原点是时间序列的第一个值, W( k3 O6 i$ s- r1 n
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。% k0 L6 W/ t# R+ J3 `9 A6 s% N
    'end':原点是时间序列的最后一个值(1.3.0版本才有)
    9 ]! c0 D- w; ?: ~! g9 V/ `. E‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)# z' P( c+ f" _% }/ v0 k
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。) O3 Q: J! q1 \. `1 G
      closed和计算有关,label和显示有关,closed才有开闭。
    * z" F7 _7 P% a% P! a. V  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。+ k$ a- {+ O; H! S3 a" L

    ! L6 C3 o  D( j( D) h) W" J重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:  t4 D& W- S- a0 W$ L* X: a
    s.resample('10D').mean().head()/ @) I  F/ a% b- P5 \. R4 j
    Out[117]:
    3 X* b6 S" H8 l2020-01-01   -2.000000; N! S+ R" A) z/ Y2 S' o. F- x% l; d  a
    2020-01-11   -3.1666672 R* _# u& v  {7 K3 Y; E5 z2 l: n
    2020-01-21   -3.625000+ Y/ @0 u' q. ?9 l" Q8 ^
    2020-01-31   -4.000000
    ! z! e7 d$ G) }) @% x2020-02-10   -0.375000
    8 p6 V) q4 ?% m$ h9 wFreq: 10D, dtype: float64  M' o* G# W) N- d3 h( {6 u
    1+ B8 j1 F/ l- I7 g% c% ]4 S
    2
    $ k9 t! Z+ N  w3
    - c. U7 [6 C2 V4" u. y# O0 x& U2 e- G5 {
    5. ~: K+ Z; G3 }4 O  q
    6
    $ H5 p; A  G8 ?2 _( W% X7$ e: @  X) ?8 K1 X5 V$ b1 Z
    8
    % j5 Y  {6 n7 m3 Z& H0 L- S1 g可以通过apply方法自定义处理函数:% v. p1 ]$ t& }) z- W- O
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差1 B$ T# g& l& m' {6 y. |5 N

    ) ]" E0 I" o& F; G; R1 h: ?' COut[118]: - S) E+ z$ i& m5 Z; A% K: y
    2020-01-01    3
    % f& F/ [5 Q% T% _/ Z4 E2020-01-11    4
    ) ]0 s+ E8 J4 }! \5 Y. r. m6 C2020-01-21    45 ^; d9 B! v1 B& T
    2020-01-31    2
    ( ?4 [/ `- K% S3 l( x/ ^0 g2020-02-10    4, E3 W- j3 [  x) x3 U
    Freq: 10D, dtype: int32
    3 q0 D3 p6 e' o2 E1
      `, }2 l/ O- E& K9 ?& u. M2
    9 Q' E7 Z# Z  F9 T  t4 n- ?3
    5 k; |' H" w/ j3 p4; u. h3 ?5 ^: M' g7 R
    5
    8 N# r" k4 J) T) y4 [" k0 Z67 Y8 ~* J: Z1 q6 D, \2 s7 q
    7
    / ?/ [+ \) \% U0 D& Z$ d6 O8
    . E( u! t2 B9 L" o0 F9
      r- B# }( w0 ]  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:+ `' @* `$ Z# \6 W2 \
    5 v3 O. y* N  @9 u- S
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
    , T2 u$ o. U. ]" E  |& sdata = np.random.randint(-1,2,len(idx)).cumsum()
    9 K1 U0 v' W0 w, K8 t- hs = pd.Series(data,index=idx)4 I4 r& P3 O2 l: p. }. e
    s.head()
    * S: e" t) W& o: C/ _1 q  P# {+ E2 p9 L, V9 z8 B" f
    Out[122]: $ e3 S' `* \+ t
    2020-01-01 08:26:35   -1
    ) s4 F, f9 E, I: @2020-01-01 08:27:52   -1  F2 K9 ^7 c" u/ q
    2020-01-01 08:29:09   -2. s$ [2 }, U' @* D4 J
    2020-01-01 08:30:26   -3
    7 o) @# B6 D* s0 \+ z! Z2020-01-01 08:31:43   -4
    5 B! x. Z% J0 `! BFreq: 77S, dtype: int32) c4 R4 U7 Q( y! B9 S2 R/ E
    1
    / A) C5 x& a7 P) t  L8 X3 {2, `) O. ?; @1 `, ?
    3
    1 D# H" [$ ^5 D' {5 I3 ?" V7 A6 l43 O: x3 v1 i0 U& b( B3 L
    5: W# S% i4 K" a: h" y( m  v
    68 A- f+ B+ j" W9 j" y+ T7 `5 [
    7
    + s3 C4 P! ]: C$ E8
    9 x+ P6 F  ]4 p- S$ U' p9
    " J$ j/ O& ~: {7 C' d+ v10
    ) L* E! b0 |0 }7 r+ H& ?) ^11+ |$ ^, e$ u5 t% r: [9 s  G: `
    12) k9 B9 v6 P0 z$ {/ o$ Z
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:; v/ S8 [# _! V0 m* q: _

    # ~& R& B, S. O% F4 m3 R3 ^0 Is.resample('7min').mean().head(): V7 N( O# N! r. V
    Out[123]:
    6 `: ~& x5 B3 {/ g2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
    % n$ S4 V0 S" w- K2020-01-01 08:31:00   -2.600000
    9 l# ]8 d2 r% M  |5 }; D+ ^/ p3 d2020-01-01 08:38:00   -2.166667
    ; E( C& w; r# s3 w. T+ x2020-01-01 08:45:00    0.200000
    0 H4 Z. K8 |( q2020-01-01 08:52:00    2.833333
    5 X' ?- q6 c; W, E" Y0 H) G& IFreq: 7T, dtype: float64: @# h& d) v0 R
    1
    8 {3 e  p$ |3 v' E/ @" G2
    ) G/ T& `- S# C: H3
    # n9 _1 T. J' s4- w0 V; Z5 [( k8 S
    5. r2 H) H+ a& U3 @/ c# v; p, }# ^
    6
    ) ^( [& V, u8 P0 b. C+ j71 Z* h7 \: Z8 ]" {* D' Z
    85 E$ ~2 L! e: k5 f1 R% j
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    / X& t! F: ?( m  J0 h# v/ w0 \
    ( v. ~% F9 i8 a0 b# T2 ss.resample('7min', origin='start').mean().head()! l1 s+ Z  m9 Q" {3 ~" _( z
    Out[124]:
    : x3 m* {3 p# B2020-01-01 08:26:35   -2.333333
    , F% V$ l5 U: }2020-01-01 08:33:35   -2.400000$ o) L, |, d+ ?# f, j% C, b
    2020-01-01 08:40:35   -1.333333$ a. F0 r+ j* l) j3 W
    2020-01-01 08:47:35    1.200000" t8 u, T5 v2 n
    2020-01-01 08:54:35    3.1666671 X6 I: c+ d& J, j6 N
    Freq: 7T, dtype: float64
    - k7 H' x' P! ~0 a. u+ O# I1  |2 [% V) s, b1 {' U+ l+ ?; J
    2
    & c; G8 I& \0 L; h7 F, {' T34 p7 x5 ^: m- A' Y$ Z
    4
    2 \5 S3 b# b! C1 q' N" [5
    $ p& P' ~( l0 I6 T2 q  g8 f! O9 J8 B6
    ' o0 b! R* }" ~, f- z4 R71 \1 n5 R% \5 g. X7 Z: X
    8
    1 i4 V& N# Z& R0 g1 I0 l! Y6 S0 [  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。2 {& g- h/ O" e! b( E7 \1 _5 h
    1 o* s8 @7 O7 n, M
    s = pd.Series(np.random.randint(2,size=366),
    ; z  Y  |3 s9 @9 K$ a2 u              index=pd.date_range('2020-01-01',
    ' I( X$ y! ]" D% L& o4 F* F1 }                                  '2020-12-31'))
    8 v# S' ~/ f8 Q  o9 A0 F  ~0 n7 t1 k

    & D6 T( \1 A- o4 B9 xs.resample('M').mean().head()
    # y# k$ c' P, H3 E# J/ KOut[126]:   T" ^, E8 K6 l. N
    2020-01-31    0.451613; A% ?1 r& }1 ~; t0 T: h6 R
    2020-02-29    0.448276( S5 R! ~9 p" Y0 [: f3 ?
    2020-03-31    0.5161293 J. Y: K3 _# P! X- H! ~
    2020-04-30    0.566667' f* j7 q3 V. j  y; y( v6 C
    2020-05-31    0.451613) ?6 G* l* `. Y, }. m6 R
    Freq: M, dtype: float642 a; G1 E4 j! E( d  p: Q, I

    * J- ]) a+ K( N# ts.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
    : _& V! b, c: h: |1 ^' }8 d0 ZOut[127]: 1 L4 K0 K5 _$ H1 g+ m" j* K; \
    2020-01-01    0.451613
    : }/ f6 X' \' R! d8 x% C, `2020-02-01    0.4482768 Z1 \: Z; Y0 t* m' N( s. o' h
    2020-03-01    0.5161294 h! d' s/ S7 g" |# ^0 [
    2020-04-01    0.566667; _& }: k7 n8 T
    2020-05-01    0.451613
    2 ~8 _5 S  ]) g0 x- D- ~& T3 \Freq: MS, dtype: float64+ q3 D" Z# h! D# y

    % o4 _  d5 l3 ~8 N3 {: D" ?1
    4 d+ B) Y- S9 c$ ]& Z7 _+ O: V4 M2
    9 W4 A6 L' w( }8 e; @4 S3
    / M; C# H6 Y1 s; @' O48 `  Z5 u: k. ?8 |& R3 i
    5. x2 c) j% ~, {3 C4 t5 r
    6* l" X3 y) t& d* j$ {- w0 C- O3 f
    7
    ! \2 n8 M/ N1 K# c8, x* T/ T( u5 p" O$ y$ b
    96 ?1 e# ~6 x8 K# N8 A; {
    10
    3 P; p: M* f1 g# |# Q1 C3 y11  Z& @8 ?3 b+ N4 l' t  Z5 q) d: r
    120 Z; O9 W6 d  d% v- n' i
    13
    9 e9 k. P" I- i148 ^" k# t: S- J1 b
    15
    2 q& d& [' g. U9 ]4 ^0 @1 k, W, J16& e  y) @* E7 m( w* v& t6 i
    17
    . H) ^3 G0 T: K+ Y1 t# [( z0 K2 n- M18# i6 D/ \/ p: s4 r! s8 _2 j' ~
    19
    " [: @8 B! U* \' y, P204 H; L; P$ N- {  j% U: G
    21
    % R. E% j6 f* ?22: V' @  ^& z6 V
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:1 a/ U0 u& v7 {2 D3 V  c
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],# h; P( ]6 F. i) A! j0 J
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}# r0 u, G% D0 _+ e( j0 v+ b+ c
    df = pd.DataFrame(d)5 O2 J5 f! e* {5 F
    df['week_starting'] = pd.date_range('01/01/2018',5 Z) d. I! d; R' I
                                        periods=8,
    ' h% k# `; G2 C; I  s! P                                    freq='W')
    0 s( e7 j2 ~# ~5 U8 Idf5 K. o8 V) _& x/ D% k' x$ K
       price  volume week_starting
    6 l: \  p# }) k; z4 z+ d0     10      50    2018-01-07
    ; z4 {: z( |- T1 G8 Y! x: [' v1     11      60    2018-01-14
    6 x. ]/ F+ Y# Y) F2      9      40    2018-01-21
    , f6 R: k$ [6 D. I- j7 b3     13     100    2018-01-28+ f9 K8 A5 V6 \5 S! e# ?
    4     14      50    2018-02-04+ O7 F9 y/ I- r; s" L
    5     18     100    2018-02-11
    2 K7 b- C- }9 ?" j6     17      40    2018-02-18
    + x2 z1 ], x+ x* [; ~7     19      50    2018-02-25
    . a. c7 |$ M8 \+ j6 a6 Rdf.resample('M', on='week_starting').mean()
    2 _4 f0 {( w5 Z4 R, S               price  volume
    3 f; u  E8 {2 Z0 t+ Oweek_starting
    ; b/ x8 V8 U. |; T) E2018-01-31     10.75    62.54 Q/ q$ G. P2 q# I7 `! M" n
    2018-02-28     17.00    60.0; @+ E8 v6 ~, W1 }0 N
      R6 r* t4 q) J$ p8 J$ M/ H
    1
    . j: ?1 D# ]6 D/ h+ M& x2: Z8 J2 D3 p/ j6 y  S
    3/ M" X* T, k% I0 |
    4
    9 r* F6 P; \2 W' X5  U. X. b$ |3 @& a! n7 s
    6
    + G  G+ {8 i  C% ^* c7 s- ]7
    ; G( J" b6 ?( n( N7 w89 h6 R* g( t9 X7 k/ q3 f" x% c/ z
    98 Y0 ^* K: A3 ~1 `
    10
    , W, @7 G9 n$ w( @& H11. e8 U$ l! p; F, |
    122 F3 \% ~1 l( J1 r, H* B7 g( Y
    13( V$ h3 Y4 z0 w& D; M7 R
    146 {* [4 v! l7 G9 }- p
    15" f, V' U, {) d* R
    16# m0 u: l+ I  g1 d
    17' v- a7 j6 G- R- q
    18/ N" U0 S8 A, }  M7 D
    19
    8 ?1 K* g/ M; o. ?) g! Y20. I! R5 l* j, n9 t
    21- A8 O4 r5 g; v& v! P' b+ O, a. ~$ H
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。, Y. R7 ?2 s4 O* ~# ^
    days = pd.date_range('1/1/2000', periods=4, freq='D')
    ! V2 q/ Y( c8 W! md2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],1 ]9 M7 ^) a" ^: U7 ?/ }3 e
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    , L- R% ^' `$ P  r, |% ]df2 = pd.DataFrame(
    # ^5 x0 M3 j: l% a" _% y5 S    d2,% x! I+ h6 ]- f8 k2 g
        index=pd.MultiIndex.from_product(
    2 ]; h+ n2 C/ [% m' d        [days, ['morning', 'afternoon']]
    ; U' V: |# S  o' R5 X# N    )
    ! L8 N! z7 ?. o2 J5 I)0 `7 n% S* r( D8 L8 _' t
    df2
    7 _# ?" B. l3 K0 \9 a                      price  volume. {2 z( c" L$ U
    2000-01-01 morning       10      50
    7 i9 m3 `5 z! d- T! N5 F           afternoon     11      60
    3 \+ _; e6 E7 m* Q: D+ W( A* z2000-01-02 morning        9      40
    4 H" K7 C- u: [) W' o# v           afternoon     13     100
    8 R+ f8 X# ^  u& q2000-01-03 morning       14      50
    % M* x& D6 z1 F# y, X: _           afternoon     18     100
    9 |3 t5 r" p  Z2000-01-04 morning       17      403 C: P( R/ ]; i
               afternoon     19      507 n5 V1 i! q7 B4 ]
    df2.resample('D', level=0).sum()2 {2 O% e. y$ m2 w/ ]1 }) F+ ~. E
                price  volume
    0 o5 m  }1 M; ~2000-01-01     21     110
    ) k! O7 F, H' N, \2 ~! g( a2000-01-02     22     140
    ! [. O: d2 h6 W9 U6 w% u" b2000-01-03     32     1507 w4 M2 x& L" N" k; t( f
    2000-01-04     36      901 z0 i( y( h' l- V

    " _3 P$ P5 P9 z8 G% o" J' W% J1
    1 K. r9 O) ]+ D, |6 t0 P3 j! d: E. l2( T5 @6 T) j; e+ }: \
    3
    1 Z" U# |5 O9 c+ Y, n& X2 d4, T) @: s% K) w3 y& u# q
    5: ?' k2 M: @! t( z
    6! J0 }# F' ]7 N2 T* K& v, w
    78 Q+ E% h9 @; G) o" B  H) r
    8
    1 a3 _0 W6 e$ o; U92 h) x8 C  Z. D6 Y4 k- A: S
    10! t. h8 c/ r$ R; j2 c# c7 {
    11
    * M- j0 W2 g5 @% k12+ c2 O/ k6 `9 N- J- S5 w
    13
    / ^4 C8 I. h. \, d# K6 i148 t% w% R  L% J5 |% c; w
    157 L/ }/ K+ Y4 X9 L, L
    16) ?  M# b: u& `5 Z, G9 Y9 u
    17
    + X4 A1 z, m$ W) R: V7 ^( N  r. q18
      Q! D' ^! q8 d/ N$ u3 H+ g19
    ! V5 E8 H0 U( q2 R20/ X& m$ r5 i. {! Z
    21$ t7 }, v- t' |8 G* C$ Y1 C9 w
    22
    ) l' T! ]/ X! t3 X4 n& r23% ?% g7 M$ r0 L. F: s' u$ i/ q/ H
    24
    ) l  k0 `/ e4 F4 H: H' e25# H/ B+ F4 W# v+ Q& U; g/ l2 ~
    根据固定时间戳调整 bin 的开始:
    1 w) ^1 X. U* H! }3 L4 v; vstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    $ o% h# `6 }) Q8 b; Frng = pd.date_range(start, end, freq='7min')2 x; V  G- K; `2 L3 O
    ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    , ?) D0 @8 O1 Q9 D% Zts4 T' |, }* K. B# T% m/ p% ], x& u
    2000-10-01 23:30:00     0
    , {6 [5 F$ u) M' x3 E2000-10-01 23:37:00     3
    ' v3 q$ n- v: w; k/ F+ k5 q8 Y* Q" g2000-10-01 23:44:00     67 B6 G, l3 t4 v* m
    2000-10-01 23:51:00     9! \5 ^* w! J2 A
    2000-10-01 23:58:00    12) }; I8 U5 ^2 g5 D( o5 y# V& H* v- e
    2000-10-02 00:05:00    15; \+ Q. V* Z1 y0 a( d
    2000-10-02 00:12:00    18, q" w! V' a5 ^$ V; n, Y" j
    2000-10-02 00:19:00    21
    * s/ R0 L+ i- _% j/ _2 @2000-10-02 00:26:00    24
    $ H* p1 X- n# EFreq: 7T, dtype: int64
    - P3 W6 u1 ~7 D6 Y( \. s! }; C
    ' s) U8 @4 L7 Cts.resample('17min').sum()2 w# E8 C7 G- \, u
    2000-10-01 23:14:00     0/ s0 t/ m+ V7 o! x
    2000-10-01 23:31:00     9* @  ?! _( M5 [* a; N% @1 Q
    2000-10-01 23:48:00    21; F1 Z* L* B' ?; e
    2000-10-02 00:05:00    54
    / M7 }  g7 z! v# r2000-10-02 00:22:00    247 L7 c) R! M) I. V5 ~2 g
    Freq: 17T, dtype: int641 |. m3 \! Q/ `7 g. n
    $ t: ~" ~. j* [" \; H  Z
    ts.resample('17min', origin='epoch').sum()2 I9 g9 f6 B- K
    2000-10-01 23:18:00     0
    ) `% e5 s/ r8 m# ?" F2000-10-01 23:35:00    18
    ' j2 r2 R6 r/ k9 E- K2 L7 y) B: N2000-10-01 23:52:00    27
    ' H8 W2 |  U) F: W- o2 }2000-10-02 00:09:00    39
    . q7 C+ C6 Y& I4 N' n; c! d2000-10-02 00:26:00    24
    + P5 [7 Z% w2 T) j" rFreq: 17T, dtype: int647 p9 ^- _4 R7 r2 x6 u% |. G! D
    5 Q2 p8 k8 ~+ c  a8 b9 I, E
    ts.resample('17min', origin='2000-01-01').sum()
    ' m& p2 k$ n  v& I6 x2000-10-01 23:24:00     3
    5 g. A% R, T9 V( ~5 x/ ]5 [9 T2000-10-01 23:41:00    15
    % @; u- `4 I4 }2000-10-01 23:58:00    45
    & K, z" i5 q& i8 g8 J6 X0 e$ B2000-10-02 00:15:00    45
    ' A1 {' L* u' b- ]" ~Freq: 17T, dtype: int64  w5 j! ~- U4 ^
    5 p4 g* N% p5 J# d. D  D
    1" ?# @; u3 s  P$ r& y
    2
    3 J  I4 V/ g: h: S5 E& }3, U! h; s- Q* I
    4
    ; e: N; R2 [: ~4 E) A% G0 P1 y5
    8 ~1 k- \6 p' A6; V' V& M. F$ ^  O2 S2 l
    79 s0 o+ @$ \0 Z
    8
    7 w. M& t6 n% j0 b9
    2 a, q8 ?5 p4 f  q4 w109 S- c  k. d9 Q" _2 y( [
    11
    " \2 u  C* ]* _$ \: ?12( F1 i' v  p7 G8 ~( |( U5 ]
    13
    2 E5 a  {. j( V' e  u. o1 z( j14& |3 w3 p4 c; r! t# I0 l6 ~6 [6 i
    15+ {- F/ \. ~: |; P8 g' e! s3 z$ |
    16  O  q( ^# N- ~, n( O2 P1 T
    176 X, y) Z: g; R! G, B# k. ~% a
    18
    8 c, r6 G" |) i# n/ L2 y4 u19
    8 V$ o, _1 Z4 u5 L20
    , \* C+ q1 N' ^  X, U1 v3 N21# F& a& V8 \3 B  D
    22
    + i! C# N* y! e9 u, ?1 V23, Z( G& p& z4 S/ K
    24
    / f3 }6 d' N. E: ^25
    . S1 c' X) I6 a1 Y/ ]0 W4 \: e( c4 l' V( v26/ h  Q! n/ E$ M
    27
    0 X5 Z* \! l  g28  b6 c5 L1 O& A' [3 a* c
    296 M, R/ V3 s" X  I6 o5 g
    30
    $ M8 f+ l* W( @# ?8 u+ j31( W4 O) a2 F$ F. d
    327 [' W9 A$ f# k, N6 x- I; a
    33
    & d6 ]% ]3 g* z! W3 T34
    . q" B* B( p3 l! P; j351 b# f2 T  H! I; ~6 D* B) G
    36
    + t1 z" F8 M- w" s8 Y37
    % I% G- M/ J% g$ y# A1 H* T7 }) I如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:! e. c8 b8 s8 G( D8 O3 A
    ts.resample('17min', origin='start').sum()
    : b' \. e! t4 O# ~( B7 a6 P" _5 Bts.resample('17min', offset='23h30min').sum()  j! F5 }8 l# W* y! d/ K( C
    2000-10-01 23:30:00     9; Q( m" J, x+ g' x; p
    2000-10-01 23:47:00    21$ @- z; E7 U! ?
    2000-10-02 00:04:00    54. M2 i5 q9 s6 u3 L9 ^
    2000-10-02 00:21:00    24
    3 c- D: D% |/ ZFreq: 17T, dtype: int64* ]$ k+ [; \' {* j8 Q, g
    1/ T; q& `# E/ c4 b3 K9 d; {
    2
    3 t! S# r4 c  M3
    1 n; z, e9 D' ^48 r$ n, c! ~: z6 G+ W
    5. Y1 i+ R4 K# G$ r; h: a/ }8 ^: q
    67 s% \- A1 g5 `! C+ n% D
    7
    0 f! k& b2 M7 i; J10.6 练习
    : @5 {6 `+ i: L8 FEx1:太阳辐射数据集
    6 a( T9 F+ t' }" d2 s现有一份关于太阳辐射的数据集:
    / u) g# Z1 X0 G
    " z5 w  p: {5 Q7 r9 i1 B. Y* hdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])7 R  [; g$ w# j' g! n. P/ h. ]
    df.head(3)
    4 E! ^: l# a& e' [* M& @2 F0 U# `% r4 v) F; s# Z
    Out[129]: " L: G$ r& o! Q: V
                        Data      Time  Radiation  Temperature
    ! _  }8 f$ O& m  h0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    5 x4 A2 u% X% c* a) T) C+ ^1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    ( n& `* G8 p( T& L! y* T" O% Q8 w2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    3 K' |5 O0 m9 U( w0 Y17 T/ Z. ?; r2 O0 L1 }4 V  R, d
    2
    ( t$ J( t! a# F, z9 _/ v2 m# d3' n5 M2 M( L4 D
    4- }/ t* p6 x- o5 ~
    5
    ( z& \0 J1 x8 g$ s( z4 O# i& c6% ^0 w& R" }( A! b
    7
    0 L: ?& A  G8 p; |. [- Q) S81 v: A1 l5 |/ d$ h
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    8 n! \/ M' P0 \, ~0 w3 r每条记录时间的间隔显然并不一致,请解决如下问题:
    % B3 e& ?( z9 [* n# R- O找出间隔时间的前三个最大值所对应的三组时间戳。% R2 l5 s0 j3 b& \$ b
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。- K$ l- O) `7 n$ e% q
    求如下指标对应的Series:" o3 W- a6 H' i2 U0 [3 h2 a: I
    温度与辐射量的6小时滑动相关系数& s/ B: L4 ~3 j7 \
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    . n- _0 g( ]2 l% c" H+ C0 _每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)& j2 x* [4 ?" }
    import numpy as np
    3 P  H  C- G8 Kimport pandas as pd- z! n; S) |0 K! F; e( `, N
    15 K5 T4 L' P  R
    24 h* c" d8 g0 g; Q0 a* L
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。9 A8 }( S; p/ @: h; z
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列6 @5 i, X7 ^( C; A; J, Q. L9 B3 A6 e
    times=pd.to_timedelta(df.Time)
      P8 H& u1 F) B2 Sdf.Data=data+times0 M+ u/ M: ^" @5 c% A$ F% J
    del df['Time']4 ~6 @& X- r! Y# n: |  V( w4 W
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留/ ^3 H1 O5 R; C, v
    df" I* C+ j7 U! i8 A
                                            Radiation        Temperature! ]9 r" K# J( L
    Data                2 p1 h  N9 @4 o' B, V; R" U
    2016-09-01 00:00:08                2.58                51
    4 ^* |1 @  k% N" z5 ^2 p& C2016-09-01 00:05:10                2.83                51. S% `2 _  j5 P! @0 Y0 V
    2016-09-01 00:20:06                2.16                51% m: ]( @4 q/ |  b. _. j
    2016-09-01 00:25:05                2.21                51
    + b. G3 T' J  A+ |% I  F: ?2016-09-01 00:30:09                2.25                512 O0 p) R7 g& e3 L1 n
    ...        ...        ...
    ' e( J1 ?& x; Q) m# O2016-12-31 23:35:02                1.22                41
    5 O+ M# z6 N* b1 S7 e1 Z  M2016-12-31 23:40:01                1.21                41% I) W: b# I+ D4 Z" ~5 k" |
    2016-12-31 23:45:04                1.21                424 z% e% O% }; A" [( p
    2016-12-31 23:50:03                1.19                415 _) @2 H. `1 T* }* _$ N: L! p' }
    2016-12-31 23:55:01                1.21                41
    5 p* T  }0 j: o; p) ?$ }
    - F/ R' ^1 R/ o- L* j( ^0 f' h: E1
    - L$ o' Z+ b+ Q9 G2* M! w+ F+ r' v: {1 y
    39 a( |! R- @- i* D
    4
    " J* \1 m& o- G# s8 m; w56 a% ~# b# x3 U% q
    6" R: _' k" h0 R! W! L+ @
    7: j. B3 W9 z/ y& k* f
    8# }2 {- k3 v! }6 D
    9
    * h- Y7 n9 q; X" w6 [: T) z10
    1 {. p% y7 z# D5 v, H2 d) K115 c# o/ T+ W0 ]4 M4 w% w
    12
    ! k7 J$ I: t1 d5 Z* H+ W+ v137 e8 Y6 b# t3 e
    147 e8 o8 M% t' l5 ?. E/ b" e
    15" g4 L$ P, E) P1 I
    16
    , U; i: o6 h3 F17' J9 O8 X; j* @: J$ m
    18
    2 Z4 J6 W2 i3 I% r# l# X6 j19
    9 }; |' [2 F/ A! U' e每条记录时间的间隔显然并不一致,请解决如下问题:
    ' k9 ^' C; }  S- u; }, ~找出间隔时间的前三个最大值所对应的三组时间戳。
    ( J8 k( f5 `. S2 h5 [# 第一次做错了,不是找三组时间戳% C; K! M) t  g' O+ H) g9 \* a
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]5 |- ^+ Q; F" g7 I, {
    df.reset_index().Data[idxmax3,idxmax3-1]
    , f( O, [$ L% B1 e5 I6 a7 n3 L# Q5 h3 x. P. [' a8 P1 @) \
    25923   2016-12-08 11:10:42" D+ W! |; r9 y( B' }2 F: ]2 A
    24522   2016-12-01 00:00:02
    2 Z% C# b$ L4 f3 v7417    2016-10-01 00:00:19
    % q: u' x7 U1 t' M  m3 v8 zName: Data, dtype: datetime64[ns]
    ' _3 }0 P! y& o# r* i3 A; l1
    : u9 K- H2 L+ G2$ ]7 p2 O9 d- X5 @% k7 }  k! e% e5 B
    3' {  m# D! @; `* U7 Q
    4
    ( Q9 l( m, ~# S& Z0 W55 |1 e  j: A4 @" @
    6
    % |2 a) g' R/ j7 b77 x/ A( O# o0 N
    8, t1 P% w; p$ [( t
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]* Y( B- n! ]& n
    list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))* W4 N; m& v4 r2 ?+ H! J0 L
    ( ?1 U1 w6 q) w
    [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    8 d' D5 }- s. P- j9 S3 R; ~# _1 n (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
    3 w; T( a& \; |7 K) ?8 x4 N (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]- c+ I& O3 M8 w- h" i
    1
    7 W* ~6 x3 U4 g) L; V6 P3 U29 V, ]' p- y; J% I1 Y# [
    3/ g8 U5 C# S( I5 m4 n) C' w
    4. ^% X: o4 Y  g9 `/ A7 H
    5! q, S( L" p" B7 p! Z% i: S1 X
    6
    ) G# i5 {5 m6 [: \3 u$ L参考答案:7 K; D4 d; d7 Y2 s- W" y
    5 c* q% E- h) @' {
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()/ k5 V! o6 z7 J4 g( _* n
    max_3 = s.nlargest(3).index
    . x/ Z) j# V/ Udf.index[max_3.union(max_3-1)]# Z6 I7 W) z) F4 D
    9 D( T* a) Y+ e
    Out[215]: 9 y+ o% Q, @: J# F: q* W4 [9 ^. V& k
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',% f0 w, e3 O) o0 U: E; M0 h0 c! ~$ p% @
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',/ i) |2 x& j1 H- Y% q7 {  c$ B
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],$ i- y, l: d3 D1 b; e
                  dtype='datetime64[ns]', name='Datetime', freq=None)
    2 j4 V$ f' X% q( |1
    ' R( H3 Z+ W" B) ]& u+ d2/ q! M' _" ?+ J. z! E+ O
    3( }, y7 h% L6 ^0 W9 n, t- l
    46 _  ?: p: s1 `/ b  `
    5
    8 U* ]' i9 h( T9 O6
    9 |; o; l4 n1 b% m3 J; O* k( L' P0 W7
    $ _( B8 Y1 G  R  W# x: k* u8% |1 F! ?7 h! N9 e
    91 ~( O$ Q: d3 T" Z1 e! f7 o
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。$ g) ^7 |" ~. ]  x# Y2 ]
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间2 _9 T: U8 e! x9 T" b: ?% {- {, E
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)& t) w+ b/ o( e$ Z
    s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)9 E) u4 a+ ?# X# T& T
    . x3 j- o4 l# `4 [
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)7 e9 U0 |7 G: s: p1 z
    1
      w- N4 ?5 O* c/ c2
    6 n! `3 O; f9 _  M3 T3
    4 f, h6 K# c1 O7 \% Q7 y3 t( I4
    $ K! \! |, T, j% D- v$ a5
    & h$ C. m( L# q; C' n5 J%pylab inline/ o( j/ A1 P6 X% r  q1 i
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50), y1 h7 A9 }8 [2 W3 n2 z! V
    plt.xlabel(' Timedelta')
    9 i5 m. \  ^( t5 p# m" o( Zplt.title(" Timedelta of solar")- A, v) d% E$ O: d$ w( c& c
    1
    5 O7 {1 Y3 ~0 g$ E2( F/ E! K% S9 x% o$ P
    37 u0 s+ c, M" y8 B/ `6 r9 I* U
    4
    0 K2 F0 e& G  f$ E# K6 a7 m' a0 v& i" d+ Z1 Y" v
    - H5 p5 |8 y) G8 z4 b; |
    求如下指标对应的Series:
    . K+ Y  M$ r& F4 O温度与辐射量的6小时滑动相关系数
    5 y8 R2 ^; G! c" z$ b; M9 F以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    0 d0 x5 V, J0 K8 u9 Z, n' z每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    ( Q+ b4 o5 }5 z/ [& ?( hdf.Radiation.rolling('6H').corr(df.Temperature).tail()
    5 k9 e  p- K1 K. @" \% t0 B
    2 j' R1 S, s, ]; J* O2 FData' P- B$ u/ U8 a. q
    2016-12-31 23:35:02    0.416187
      D. Y2 a% p" T: m0 O" W: t2016-12-31 23:40:01    0.416565
    . R) b7 F) O) H# j1 u0 l" h2016-12-31 23:45:04    0.328574$ z( w& d8 E1 [. U. T& h$ E
    2016-12-31 23:50:03    0.261883
    % Y1 K& Q6 i+ ?5 @; v2016-12-31 23:55:01    0.262406) n& r5 G  `% X/ N( ?/ R+ `) y
    dtype: float649 }( W+ _5 U5 Z( }/ ?7 x
    1
    ' g) j; J' l- U7 p) E9 {1 X6 K$ e$ F2
    " Q# H) t% W& ^, m' d3 e; h3
      k2 s0 n( [1 i3 @7 i+ h7 Z4 f* ~/ w4, s5 J, n4 r0 x$ A2 |
    5/ h( m+ i5 P; w- n1 ^; p& ?0 s# ^
    6
    ' s) ^3 k2 i# X) b: F; Q, D7
    $ _5 W3 V2 c# X7 g6 n1 S8
    5 a" M6 s; S8 x: u2 g9
    7 j7 e8 k# W- d' ~2 {; W+ _df['Temperature'].resample('6H',offset='3H').mean().head()
    8 t4 g6 c' g1 M% s  r- U2 O, \) M# O
    7 l7 @: Q; X2 P! X+ h1 xData
    " K; F; V2 T* S% @2016-08-31 21:00:00    51.218750$ H1 K3 n2 A7 N% {' A
    2016-09-01 03:00:00    50.033333
    . }) _0 g. R2 [2016-09-01 09:00:00    59.379310( F) m: e% n4 R* ]% v2 `+ A- Z2 q
    2016-09-01 15:00:00    57.984375
    ' J0 Z4 v  W! J2 {* @; U2016-09-01 21:00:00    51.393939" h4 C' ]2 Q9 g0 W' N' K5 A
    Freq: 6H, Name: Temperature, dtype: float64' D1 u( W! J" a& Y  o
    1
    8 q/ u# w$ L5 A& Z2
    ) \& U0 V! M& `3
    5 ^# f, U3 h7 v+ d0 T4* f+ h& n3 Q& q0 u) G
    5* Q0 `! D; F# s' v
    6
    6 U1 d( S" M, X7 G9 R3 r& S, E76 r0 G9 O, w# H& `: F* J8 ?
    86 t3 e6 E0 E1 @. B* K( V4 m/ ^. M( w4 F
    9- x8 p% {! K+ ?: \/ T3 r" S& Q
    最后一题参考答案:
    * y) i7 D& y, ~% D" A% D6 w- z8 W, C  ?4 y
    # 非常慢; H% U+ y$ e0 e% N1 @
    my_dt = df.index.shift(freq='-6H')
    ! G6 P; Z8 M4 X' Qint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]9 D/ W! E0 Z. g& Q% N- H% x% K
    int_loc = np.array(int_loc).reshape(-1)3 o/ {3 J$ U) y
    res = df.Radiation.iloc[int_loc]# X/ k( i+ V; `& q5 @
    res.index = df.index; h1 j8 V( g3 ?  r9 a
    res.tail(3)+ P# `$ C! ]& A% e' z- t  p
    1! J& _- a" G# s% Z, K- F
    2( Q, {6 F  L, l6 R
    3
    5 L. X3 f' a  U4
    ( q# Q) n8 ^& c3 P. V  t5 N5. p! }) C; r; n( ?, x
    6
    3 x  Y* m% @; a; W& x. @$ j72 e$ P% U) t5 N: A9 W: R8 i- g
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    3 S) z7 M& |4 \  Y4 ^/ ctarget = pd.DataFrame(
    2 A* g9 ]' j+ R4 _# b+ e' p6 W' b    {, V  s) c' o5 a  A) N
            "Time": df.index.shift(freq='-6H'),
    ; R# N$ h7 H; B7 [        "Datetime": df.index,! g4 P6 ]4 a. k
        }2 M# n* Q* V. |9 D  I* F5 f) f- o
    )) B- ]% c' c, ^5 x3 ~3 x/ e
    . z8 V) I: V$ b' w
    res = pd.merge_asof(
    ' q1 k9 Q8 c( I9 x$ b  p    target,) t" |" L6 S1 e- h( v" |- g
        df.reset_index().rename(columns={"Datetime": "Time"}),
    ) ^9 }! v5 s# J4 b3 w7 w7 B% j- u    left_on="Time",0 n) z4 e$ |/ F) A
        right_on="Time",
      t: }' N0 `4 \1 |% R2 N1 a    direction="nearest"4 h5 L! P9 \* B1 q8 O' G
    ).set_index("Datetime").Radiation
    0 j* x( n( B$ z0 S
    & _- V: z* f4 H: g! t" {res.tail(3)
    # d3 t. M; p& O3 k( H' bOut[224]:
    1 G! b9 Z7 R, q5 r6 U" JDatetime+ i8 b' K# Z/ A' y3 F( T# R
    2016-12-31 23:45:04    9.33+ O7 v3 E( ^( B) ?7 K
    2016-12-31 23:50:03    8.492 s' Q/ {2 o8 f
    2016-12-31 23:55:01    5.846 |5 r6 b6 @( |+ }/ s
    Name: Radiation, dtype: float64: |+ h* b" z& ]& d$ a
    + J* E, q3 g/ |& r$ J' o
    1& G$ N4 O1 g9 U, v+ P6 I  k. L4 X
    2" e4 v! B! o& k5 h# L) D# V
    3
    ( u, W# W) n  K) z1 U- r4: }# V& d# K0 a0 O
    5
    9 M; v  E/ S: {' I6
    / m" \5 c% Y+ Z- H7
    9 ?, D$ M. E' a9 {$ y+ [+ L: {82 P- L% z$ F6 c4 _" K" G, X
    9/ m& j) l* _- }4 m1 }% _9 o
    10' @' l- t& u9 I* O6 Y
    117 ~1 t/ e; o  V6 g1 p% x  C9 m5 k, t
    12
    , |; k0 j! b) [* e2 a2 v5 w8 v13, Q# J1 a. J  c0 u; Z2 E
    14$ Z1 k1 s8 h3 @, F( w9 {
    15
    , G& _, P- y) c; l, |9 e. y16( A) M( k$ k- u
    174 @; P: Y  O7 f7 z8 @( Z
    18
    $ e9 X+ \0 P2 D4 o198 Q& I1 S  a8 w! n( m( k; A  L6 @" g
    20
    ) [) O$ ]+ v3 p3 S! X! f- n: ]2 A21
    * m6 X/ e: V8 ~* c  e. N22% {+ g: y  i) m- r
    233 r7 b* T. R% Z
    Ex2:水果销量数据集# W% L5 J$ q! B5 _% \, f
    现有一份2019年每日水果销量记录表:
    4 g" ]2 _' M' r; p6 R7 D7 W- H$ t+ t/ z! P$ P2 Q; B4 a+ G: [. d
    df = pd.read_csv('../data/fruit.csv')4 S2 @0 y5 r1 G
    df.head(3)
    0 v; j: J; S$ W  }0 v! v% d6 b$ s$ O
    3 b* C7 a8 o8 S; K* J( s* AOut[131]: 2 a5 E) ^$ r6 i: [
             Date  Fruit  Sale; {0 b5 |6 i/ p1 d
    0  2019-04-18  Peach    155 g! n2 J3 ~$ n( |6 P6 Z# N/ T
    1  2019-12-29  Peach    151 Z& f0 m' R, x+ h& c, H) M
    2  2019-06-05  Peach    19
    ! Z4 \% k- ?- b* c, d0 {7 u3 n1 v1
    - ?* q. W3 p, s2$ Y" P- d  u# W# ^, w
    3
    ; R% t- L% M3 ]* O4) B6 ?9 y* e' q2 d. z$ m2 `
    5
    2 E, I2 U8 e, o' M7 {6
    . _; Q8 i" G5 R% B73 K  F- J  c) `7 O$ \
    89 y# f" U, T$ e  X6 B
    统计如下指标:, @5 _8 n! W9 o. k0 o6 W( H
    每月上半月(15号及之前)与下半月葡萄销量的比值9 ]  C/ y: d) ?
    每月最后一天的生梨销量总和. y" s2 f7 [& ^4 B6 B
    每月最后一天工作日的生梨销量总和
    7 A* X- M' M+ t每月最后五天的苹果销量均值
    2 J7 m* e7 }6 B  O按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。) w* A' p1 [3 \  g8 {8 k3 e
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。7 V% p4 X! H! w5 f5 Q8 S* D
    import numpy as np. J4 M- A' z  `' Z& W* p% {
    import pandas as pd
    9 ]- k4 P3 h  \2 M) W1 n2 k9 n% _* Y/ \1
    5 T3 a% T+ D0 T5 `8 Q2
    0 g) v* v7 |0 f) ?统计如下指标:
    4 \; Q+ G# S# g: G; m, b- W  b每月上半月(15号及之前)与下半月葡萄销量的比值
    ( ^& ?, Z; z% X. Y3 `/ t每月最后一天的生梨销量总和
      {/ z0 _4 b: f每月最后一天工作日的生梨销量总和
    3 ~, |  Y. K# z5 e9 G# C- I  F# P; ?每月最后五天的苹果销量均值: E, t) I% w  l8 B8 J
    # 每月上半月(15号及之前)与下半月葡萄销量的比值
    + V  o; W8 ~+ \! L% b3 {4 Idf.Date=pd.to_datetime(df.Date)" h6 x. s5 r) z  @
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()3 U1 p) c6 R: `7 _7 q
    sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    % z; b3 Q" e' nsale=pd.DataFrame(sale)
    " _" W% f% e5 }5 ]6 A/ C7 ~; r5 \sale=sale.unstack(1).rename_axis(index={'Date':'Month'},( g/ A" f" B0 a0 s3 {" m- @5 p
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名) G) h4 F: S7 {# S5 j) v
    sale.head() # 每个月上下半月的销量
    $ V. u* ^- M' v* P, ^- |& Z$ ^9 R4 u. h  h
      Month        15Days        Sale0 I! \( w: `" N: S# B2 k5 w4 n) v6 {
    0        1        False        10503. q4 q0 D, }  F0 |' x* f
    1        1        True        12341
    ! D$ m+ d$ j5 L  k" _! w$ t9 Q2        2        False        10001
    ! A% g/ u0 b5 A2 J; B3        2        True        10106
    & L/ {. J( ?7 i4 b7 J8 N. T4        3        False        12814* A3 G9 l7 a" A& p- P* q5 [0 w
    / E; i2 m& j) T* @+ {! x! G4 h
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序' E; R3 q( K/ C/ }
    sale.groupby(sale['Month'])['Sale'].agg(
    , O. {/ ]0 n/ L; z+ O                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    8 _' q# H9 c/ p( n6 N" M
    8 t: r8 `) W$ _3 K! J( eMonth
    ) H, M/ y* J7 |- c1     1.174998, l9 e2 u$ V* ~- R3 {- Y9 X
    2     1.010499
    2 K7 B: J3 {' y" ^% x7 A/ d3     0.776338
    % T# {  L  w7 d( |: [7 h4     1.026345% c9 i9 Z6 b: }  Y
    5     0.900534* F9 r+ q1 b" f4 n# N- V8 G) z
    6     0.9801361 _. Z% _  t+ `1 k
    7     1.350960  [' i: b$ w! V. c0 i/ g
    8     1.091584
    - d$ p* S! f; o# U$ ]  {  h9     1.116508( S- s' E( u: l! @& M1 |* m4 Q4 J
    10    1.020784# }3 ~( ]- C. v& Z' c: c
    11    1.275911
    ) c) \5 b1 A! O5 K. j12    0.989662
    ( Z( I' t$ M8 \/ s3 }0 `Name: Sale, dtype: float64
    " G3 W$ }6 t7 _& ?3 S( B% ~/ T& k
    " U- H5 |+ K' l5 E5 Y$ C13 k7 K0 P0 V& ^8 i0 r' h& Y, I
    2
    . U4 Z( j8 @/ h+ n' t7 M' @3
    ( u, j' M. f% _1 L6 }42 R% m- g) b# p7 H+ \
    5; t. J% m" m& z  h+ e4 a
    6" a  b- G" n) v5 R
    71 }0 t$ B$ Z4 W. {, w5 \  q& Y( m
    8
    ) M+ ]5 K  `7 C" G- f6 l0 [) {97 o: ?% P0 r4 J
    10  s7 Y, C5 J% \) r7 g, L+ D/ h  A* C
    11+ G7 i7 }& Z% {% D" I, E
    123 L4 i+ t3 ?. A) i0 H
    13. M/ r9 U3 {2 J" j9 Q' ~0 @  x/ J+ e
    14
    ; E9 U2 J9 q! L, G15
    ( p! v; j/ `" K& r16$ _- a' y+ Y7 {& t' C2 p$ W( T
    17( D+ r# `+ F7 n+ W# g
    18
    5 L6 }6 Q% ~7 g$ m; K  e2 P/ i192 a: N: Q/ ]8 K
    20
    ) z6 @+ r5 y: ]' c# e$ U8 q8 k21: o! S7 x% D3 E, _  I
    22: s( q7 _; X7 Q; e
    23
    2 ]8 N- V3 P6 I) l% P  H- E, _0 v& ]24
    7 F( d' B9 g* e1 Q1 g2 `2 S7 o25
    2 c! E9 Y  d1 h6 a+ y$ k26' J/ ^" U9 e: J# _- i! |
    27
    # a. R" ?$ G9 z; s- Q280 ]  A6 ]8 ]4 e8 P+ I& A
    29  {* @0 L6 ~0 }0 M/ ~
    30
    / }  u! C- K& i: o9 g- R31
    9 i. C3 {4 j3 Y32
    ! f$ y2 Y4 b: w% s9 D3 X1 i/ L0 a331 q9 N0 m$ w7 G; I1 `( N; G
    342 h, |- R( M* d
    # 每月最后一天的生梨销量总和% D# q+ ]2 ]( P# b
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    6 c  I4 r3 D3 _  J) G; e. Z; ^
    & b: F6 T, Z* g3 |2 K! {% a" aDate
    0 U7 ?) E. |4 I2 ]2019-01-31    847% Q: b+ b2 @& ~1 }8 C# t
    2019-02-28    774
    . K- E1 V2 _3 @- ^6 i2019-03-31    761
    ; M( C: C' e. A2019-04-30    648* o& x7 k. v/ X9 K8 {# g0 p, w
    2019-05-31    616
    ) \. T  k! O1 t4 [) q3 R) ?% Y) g/ e. d1
    4 c2 l% N3 _- ^& ^8 d, \2
    ( N8 f& l1 I  D3- ]4 g$ q9 B& Z5 E* A( v) `: H
    44 O' N  G% }7 z  V
    5
    , ^, a8 t" \6 a" t8 t. _6 r7 D5 m, b68 H" k: X: F. Z- @9 g
    7
    % P" b  j( i6 F$ W4 |% X8
    0 A2 L4 V: ?! X, r- C1 @9
    " Q, H. N" I' a# V; C# 每月最后一天工作日的生梨销量总和- G- r8 c8 \; p: r* o9 H
    ls=df.Date+pd.offsets.BMonthEnd()
    ; ~/ h" I$ W. Z( N, Ymy_filter=pd.to_datetime(ls.unique()). |! i$ X5 p( {! Z8 o* T2 Q
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()7 G! e4 c' [( K2 G$ e4 _
    ! ~+ k  I7 n7 t! [, H1 {
    Date
      {/ Z7 ?( ]) P2019-01-31     847  R) S, V$ ]' N. W9 x
    2019-02-28     774
    & A1 @$ Z+ i+ G6 ^; C) V2 g& `2019-03-29     510: y% N' J3 I7 f
    2019-04-30     648. L7 `6 O8 ~9 [. b9 d8 S2 O
    2019-05-31     6166 \/ ]3 s3 @$ N) U0 o# J
    1, _* c4 X- `% ~  V8 I: F& B) h5 u
    2: Z: |: W7 m' r. A/ E: D
    3. N8 s7 X  \: a# q# y
    4
    4 x3 D# T9 y% r  U' I5/ {: D" }' Q) Z) {2 A
    6
    $ O; f' @+ r/ t  S3 U& [3 E0 |% h. s  Z9 w7& e/ Y% [7 S# A0 K# E  y! L
    8* N/ _" P7 k( M  D8 Y2 r/ s! f1 r; T
    9. Z! J7 G2 s  {6 k, z0 ^
    10! g8 B3 c4 A5 p- C& W' k9 ]# P6 L
    11- o% g9 u1 F$ U( F
    # 每月最后五天的苹果销量均值
    1 D4 M( `7 v  Y1 xstart, end = '2019-01-01', '2019-12-31'
    " P+ M6 f1 [  F  R6 U, P4 z4 Q' S! ^end = pd.date_range(start, end, freq='M')
    $ z5 H9 \0 j# G& d+ Wend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    : B$ l) x" o! J1 Y$ e8 Y7 c$ c1 z: V  x0 v# }
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)# y* e2 z1 G* e' [
    td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天  h2 A7 D3 T' S+ K% p7 ~
    end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    4 \2 V, ?4 Z2 y- |& }; D2 H2 ]9 a6 H; C! P3 m: i$ Q" J
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量7 M* }; a' {* G5 e# K2 H4 ]2 Y
    apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head(); z$ J. V8 ?/ U+ j& P

    , ]3 |, |9 u% vDate9 y& n4 l  z3 u8 e7 @; ~, z. w
    1     65.3137251 m( F0 H/ `, m" X+ g
    2     54.061538
    * A. x' X, f9 Y4 d3     59.325581
    * Z6 i% A# x7 H+ G4     65.795455& B3 T9 f/ D" ?6 W7 u. g% {
    5     57.465116- L6 u; ^" w3 \! v; Z' ]) x' F
    ) x9 [6 F" W2 }- O9 ^1 ?
    1
    : T1 r; `$ M  Y0 K) ]3 }' i  ~2& [8 V; E& C0 m. t
    3# M, c1 M4 z" C  E  N; i% |" l
    4. A& l* z# C/ c  m
    53 K4 \7 n  G) e/ m
    6
    2 o+ h7 Y; p! G. M; o) N6 i7
    . g1 `2 _- ?( f5 @6 ~8
    $ x9 Y& K2 [  d  T9
    $ `4 {% |2 f- R7 L9 g( `10+ D/ h9 M# i; A' S+ ^1 [0 q# G' ^; F- [2 U8 q
    11
    ; ~1 ^3 z$ o5 ?) O( i, l* b# Z12$ I6 F4 j" F  h3 Z
    132 i5 g1 g* v( ?) v- U, W1 G$ E/ C
    14& z: f, b6 F: `4 m9 n
    15- n! m+ Y% J8 H( ], c7 S
    16
    % j  @* T3 c! R8 ]7 F; |' A$ S177 Q4 ]3 b: m0 C4 A: z( e/ T
    18/ f6 [% {; v. _1 z# j7 c8 e
    # 参考答案:
    2 [! c+ ^/ \8 `# Rtarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(& n; t* a% V4 [/ k
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True)% g2 \) u$ `) v7 I- y

    5 A$ @% k8 T+ ?4 Tres = df.set_index('Date').loc[target_dt].reset_index(7 O$ R$ r2 S- N1 D: |
                ).query("Fruit == 'Apple'")+ v4 B: x4 g; h& ~' U) j" J

    1 c0 _  V$ P5 Z  J0 P4 N1 Wres = res.groupby(res.Date.dt.month)['Sale'].mean(
    3 S+ m6 ]4 M: J3 M! e            ).rename_axis('Month')
    3 o. A6 X* P9 y- N3 o
    0 _3 f' c: ?7 r' @
    8 j  L, ~* r5 J* d; ^1 ?res.head()7 x. S- U7 A4 Q8 |4 V) N0 P* F7 `
    Out[236]: 5 `8 ]! ~4 _; S1 L9 m0 }: e
    Month
    ; u6 {# T$ T( }: H: m' O1    65.313725
    ) F( @. x# Q" m- b+ F2    54.061538% h" z/ L6 B; R( ~0 h+ e
    3    59.325581
    . s: `9 }! k' s4    65.795455
    ( X# V, H* l2 m1 Q' X5    57.465116
    : z* c9 [: _  C. N: d, J) X( @Name: Sale, dtype: float64. e, p% B1 O; S' s
    ; _4 g: {& Y8 {! L9 A9 S
    1
    # w" D* u5 U# q  r2
    ; B0 T6 O# x* J: w. N  O/ D5 C3! I5 J. ]* P7 ]' I; }
    4
    . n3 m+ ^6 Z' ~0 F5$ A1 L. d4 G! |9 N
    6) z7 n1 |1 D. c3 O: y- W
    7- C7 }# s+ j% \5 x
    8
    % c! p( x' S# E) C4 E, E98 T0 H: V! ~& Q: I2 g
    10
    / C* V3 v9 V; x3 q  e8 E$ B11
    " K5 b+ Q0 B8 ~% l8 ^* G7 u! m( {122 {$ ]: W* Q( k$ f4 U4 A- Q( x
    13
    & f4 N- j) t1 O5 b14
    # t) B& D, f$ f0 [. D15
    ; `0 Q, k- O- C5 z$ U) @; s+ Y+ O16! m0 ~* E% ~) I: o9 g8 _
    17
    , R7 j  f) z- T% [180 j4 e2 J  ~! i( _; V) Z
    196 h5 ?6 P' f$ I$ c1 H
    20' A3 A" h4 s* t, {* z% u8 a
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    5 r% |, |2 s! f& Y; E* lresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
    # g0 V: ~8 a& X1 A                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
    2 i; J% D1 M$ F2 o                                       
    2 G. {: ?$ m" c8 l  d8 rresult=result.unstack(1).rename_axis(index={'Date':'Month'},
    " v2 ^) K( C( }* P, s9 Q                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
    # J# C8 j1 w" _3 Lresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
    " O  n* u& c# E. z9 T. U& cresult.head() # 索引名有空再改吧
      [+ S6 m! H) b+ w1 E' H6 b1 N4 B# t# @: X3 k8 {
              Week        0        1        2        3        4        5        68 q5 Q% U' V8 d* v2 [
    Fruit Month                                                        ! ]% E6 c9 X' ?4 @4 S7 @
    Apple        1        46        50        50        45        32        42        23! q* I' j6 c: I
    Banana        1        27        29        24        42        36        24        35
    ) S+ _/ z. C6 p$ g0 p! zGrape        1        42        75        53        63        36        57        46
    & C( m3 `8 o3 |Peach        1        67        78        73        88        59        49        72
    9 d' N, ?2 _4 f- k1 D- |& oPear        1        39        69        51        54        48        36        403 H5 p# j! ?0 h$ Y! p2 X
    1
    + p  u1 r7 U% o9 D0 \0 Z2
    + v. E% K! k( m# R5 V37 H8 Z  a) N- J# I9 Z" y5 F/ v
    4/ v8 h& |4 Z2 D8 e3 e: ^" [
    5
    5 N; j% p6 x0 F7 A/ n0 w6
    ! F# E9 c' V* d# U$ A4 T70 m6 t, D! V' O  `- h% S' i# O: j% a
    8
    + o; {* y4 M* `" [# P9
    8 X" t( _* T- A* S1 R' {. X10
    * ]- c8 }2 g8 z11
      X8 c9 ]( Z8 q12
    $ |0 Q, v2 y6 b) e7 s2 p13
    3 S7 G  d+ a. C. j# a" D+ u/ a/ e14
    ) Y: ]2 h( y% {& J) z15! U& W2 |( h' r0 x+ O: a; o4 ^% p
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。4 ]5 w# }* m# G5 @- L3 W
    # 工作日苹果销量按日期排序/ q( a; ?( [+ c+ X( v) L) _, I9 W
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()/ \! @5 o8 t* u: W4 s
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总! I9 u  c* J/ v+ M$ N6 E
    select_bday.head(); l# L9 g8 P7 x+ z% o

    * T, U6 N" G3 EDate& \1 F* p8 A7 m
    2019-01-01    189
    ! S" y( b; `1 Z9 y8 s7 {- v2019-01-02    482
    : v& {' N8 _1 p  O9 b, n. N2019-01-03    890
    ( Z6 d" o  m. l9 Y1 L+ j  c2019-01-04    550
    $ f" p0 _, Q5 e. ^% }8 m  u0 g) J3 s2019-01-07    494
    4 O& l* i9 M3 e* M1 ~9 ], \! C1 F
    ( ~3 g! W( T. k: v# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。) q( i1 _+ [! z
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()5 Y# B' Z4 l2 z4 i
    / {/ W$ o: P* Z/ z* J6 Z" t7 t
    Date$ m4 f! C  @0 U( O0 o* C
    2019-01-01    189.000000
    5 ~* [* n* A2 a9 T2019-01-02    335.500000* ]. ?9 Y5 \+ |
    2019-01-03    520.333333
    " I0 p8 K/ L: b8 K6 S# j, a% H) i6 q2019-01-04    527.7500004 H2 P/ u  R& h' U# a
    2019-01-05    527.750000
    ' p: |6 x! {1 T% C- Q4 @4 @* k: L5 P$ X- h  n. B4 Y
    ————————————————
    ) f5 M; {3 M2 |- k版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
      S* W. i2 f' m6 r: x原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    & O  x* W9 {: x1 X
    * n9 u6 p* h8 n9 `. v2 Q4 ]- B0 l1 {& _- p
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-4-13 10:42 , Processed in 0.597920 second(s), 50 queries .

    回顶部