QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2894|回复: 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
    9 x% O$ H  `6 u5 G7 L+ l

    8 P6 ^' |6 E! T' D4 @# @* K
    " ^4 M( B. d6 A5 V4 `( H8 k文章目录
    : i' k0 W3 e8 J% _9 R* f第八章 文本数据
    # c# U4 {0 B$ d- u- `8.1 str对象( [5 Y, N( _) Y# o; |0 R
    8.1.1 str对象的设计意图
    8 h. a& u/ E' P8.1.3 string类型: ]; }7 H% a& a
    8.2 正则表达式基础
    ' B5 W) I! J, p8.2.1 . 一般字符的匹配! n7 U. {# I2 ]
    8.2.2 元字符基础! s7 V( F* N* I/ I$ B% @- g" Q$ ]
    8.2.3 简写字符集
    . t) m) u0 M- _5 I0 J8.3 文本处理的五类操作
    6 l- ]+ c( w0 o. `1 }; N, F8.3.1 `str.split `拆分
    ; J0 J* U: `8 z0 G! q4 n2 j8.3.2 `str.join` 或 `str.cat `合并
    ) W5 ?( Q, m  u" c+ C$ H0 \8.3.3 匹配& z" ^8 x6 P" n0 R+ z: A
    8.3.5 提取
    6 i1 I- M2 X: v7 t1 o8.4、常用字符串函数
    & y9 p) l) k1 ^; j% p2 B, B8.4.1 字母型函数
    * }0 _7 O& v, R9 h$ u8.4.2 数值型函数
    5 \) C! u5 X4 k0 R* o8.4.3 统计型函数
    . r+ X  J! h8 A+ y, o" M: {8.4.4 格式型函数
    1 i% c/ ^7 S$ s3 x- S! C" i4 U8.5 练习
    % G! z: _7 `5 D( i6 {3 b2 YEx1:房屋信息数据集
    4 _0 b9 t: i! S) tEx2:《权力的游戏》剧本数据集. a4 a$ P5 R" w0 y3 w) B0 ^
    第九章 分类数据
    + _* _! z% h& o' ?- L& S' e* ]# F" d9.1 cat对象
    $ z/ p* ?# E6 \& C5 {. N, ?8 O9.1.1 cat对象的属性4 K+ x" M9 {" B
    9.1.2 类别的增加、删除和修改
    , ?0 a6 E% V6 [' Q" J; h9.2 有序分类1 }9 y& N1 ]9 f+ s4 l2 L
    9.2.1 序的建立4 M, s! i) L9 [& r/ @" X, Y
    9.2.2 排序和比较) o7 u/ n& Q, M9 k; c1 ^! m. d$ m. |7 x
    9.3 区间类别
    6 B4 ?+ J3 b' R: w9.3.1 利用cut和qcut进行区间构造
    / g/ L/ @1 C% y( H, z# M. z9.3.2 一般区间的构造4 c; Y! l; a1 R, |
    9.3.3 区间的属性与方法
    & Y8 r- d/ `9 i& }0 C# E9.4 练习
    . [1 y/ {/ O8 [3 _Ex1: 统计未出现的类别+ G# k1 @" `. g/ X0 H" f
    Ex2: 钻石数据集
    6 J7 @& J& g4 G8 e6 R0 c第十章 时序数据
    4 @. ]1 U6 J' ~; T; t. J6 q* f10.1 时序中的基本对象# C) l) C( n& @% j9 J- o5 a
    10.2 时间戳; u  g. M6 s" m6 V. |2 G
    10.2.1 Timestamp的构造与属性! Z+ d) N' S, u8 i
    10.2.2 Datetime序列的生成5 r" q6 j1 T: _
    10.2.3 dt对象& v1 y' J8 C# L' V/ S$ W( e$ i9 ^
    10.2.4 时间戳的切片与索引
    . i$ R# ?; m/ K% ]: D10.3 时间差
    9 J: j! I8 Z0 G; Q3 p! n$ ^- l10.3.1 Timedelta的生成
    2 I) g3 v1 H' ~. q4 V( P) X10.2.2 Timedelta的运算
    1 J) Y$ Z; F7 p0 f7 F0 @10.4 日期偏置. }+ Y& x- y' _1 N% c8 @+ L2 K
    10.4.1 Offset对象( X1 N& F' }, F5 r/ s
    10.4.2 偏置字符串& S1 o) y- `2 k4 s: F
    10.5、时序中的滑窗与分组* t& \( r4 c$ R
    10.5.1 滑动窗口
    ) t) B4 @- d5 l, d2 O% E4 p6 {* g10.5.2 重采样( I- ?- o  g; R1 ]
    10.6 练习
    9 x0 G. Y& A/ Y4 y1 p3 O- |Ex1:太阳辐射数据集4 }6 E2 b: E+ \, g* Q/ g
    Ex2:水果销量数据集# a! {$ S4 R: t( v8 u5 a
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    4 B. W# I4 F. q6 N" H5 S" }传送门:
    5 P+ k/ d, ~! Z: i
    + s1 Q+ O# W" \4 J9 [# ~datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)3 M9 t) |: ~% M1 F" V, {2 y
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)5 c" H# f2 r, `$ d
    第八章 文本数据
    * t/ X- `6 D7 T8 K8.1 str对象
    , Y$ j6 p+ \* M3 J6 G2 O+ J/ D6 w8.1.1 str对象的设计意图# h3 I, K6 l* }+ I. Q* s7 U
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:5 X5 U% C: q# O/ @1 A
    1 ^5 b+ U, C$ G  f' J4 l3 R8 l
    var = 'abcd'
    % q$ B; t% G/ S; M/ bstr.upper(var) # Python内置str模块& \1 B4 f& U( O% C; {3 J' M; T& }
    Out[4]: 'ABCD'
    1 @, @2 v+ w. K" \9 z7 d: R6 O! l7 c6 H2 R
    s = pd.Series(['abcd', 'efg', 'hi'])
    0 }* f* Q' u# Q- B' c1 _1 V/ H# U3 x1 `5 I. J
    s.str# h: y2 E) m; i- X: m1 x. T! o1 P3 ~
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>" \0 o$ Z# @* I1 ^

    1 m" ?, J5 Q# F) J) i; ks.str.upper() # pandas中str对象上的upper方法
      N; ?$ U  ^- O$ w  i1 p  e5 E9 AOut[7]:
    . R0 }; ^; w6 @/ O! b7 C) v0    ABCD
    9 o4 a# F( ~' {. _; f9 E1     EFG
    0 {$ F4 K4 |" _. q2      HI6 k! W* p1 I6 \0 f) ~- f) Y
    dtype: object
    ! V- y( A; c1 A6 s1, \% s& ?( a5 S( ^/ ^7 M
    2! a  t4 K* N5 ?& ?" x# u
    3
    7 f3 d! ]4 Y# H, d5 m: ?4# n/ W3 z, [, y& f. K4 G
    5/ B1 |/ P$ i( Z8 H' M3 P7 Q/ v
    6
    1 P9 ?, I* A8 i  E, }. O7
    ' H' M+ g  U4 a, A) G8
    3 u( c; M- F) p9
    & P! o* ?2 u  Q! X1 C, g10/ j( @) ?3 T: ~9 C+ P$ P5 `
    11$ @) h( |/ O' f# Z, p* V. z
    12
    6 b5 o  l( s! z3 n- `13: C, M* |& Y# b' K0 x7 b. g
    14& @7 s2 i' g# Z& y+ _) S) M  E1 f
    157 {, k9 \' f' _% f# D3 _1 n* n4 f  ]- O4 K
    8.1.2 []索引器
    : H- ?' h, [' p! K! |  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    9 e/ `. g: r6 p2 d  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    % n! o& u' L9 n+ O- K
    # [+ ?" g! D0 Y- w/ |7 C8 S0 ?s.str[0]
    2 t" f1 J$ R- V. F( f9 l! DOut[10]: ! u5 P2 q( i  L2 U4 N' o( J# b' `
    0    a
    ; U) w1 G) s, A) ?1    e% h! Z; e2 I7 @2 O8 [* c. K$ B
    2    h- G8 T7 {6 E/ I; v# s/ ^
    dtype: object* d. q8 g$ X* D0 W

    - ]8 Z4 W0 I4 Gs.str[-1: 0: -2]5 O8 W" ?8 w$ }" F
    Out[11]: * H+ o0 c( g$ C& q7 B$ [
    0    db6 l+ p8 D9 H" y7 {( s
    1     g
    9 o8 t& }* N/ ^. T1 e% X( R0 [2     i6 `, g5 B6 f5 X) x' E8 c5 V
    dtype: object
    % f* O5 ~; J! h; g* h% @
    2 P1 g8 L# ~2 r1 O' Z9 ]s.str[2]9 c& \4 ?" @2 a$ ?# c% R: J3 o
    Out[12]:
    % p9 ]# A3 x& p0      c5 e4 o5 T' w5 o2 G' r- D- ]4 M
    1      g
    8 {9 W& |; n/ \4 n. P1 ?0 h2    NaN
    0 ]+ s  Z3 W3 p9 ]7 bdtype: object
    0 j* c$ a1 k0 ]4 T7 l8 {" B! m5 E2 p7 |1 U
    1
    2 h0 T$ {9 Y6 J( `* f0 R2  U' v& j: ]* H+ H0 h+ O3 B
    3
    ( P5 m( L+ k* C5 d/ _! K  L3 j' t4' K. Y3 S2 `/ K$ R* _' Y
    5- \' B7 j2 g+ S
    61 ]$ \# ~4 G+ A) Z
    7
    - y. u4 B- K; s% m# T8
    1 N* N" D, s% }8 s9/ J! f7 ^: K- @% V0 u' s
    10
    5 E) s6 C7 ~) b3 h9 `11
    6 r1 t. A% j3 w' t12
    9 |$ l1 {0 p+ P9 n  c13
    8 B  O1 T: ~& t! ]14, m" W9 O  A' \- X3 k; W. @& g: [
    15. _0 p3 U+ m5 A) H
    167 e/ Q( H1 c9 q1 d( V
    17, {* I2 D. `/ e) G2 ^
    18
    2 P9 v; N5 T+ E5 A* K3 O194 ~6 x6 u3 G  z: U
    206 U  A. c8 O) _1 j
    import numpy as np
    ' z8 T" n# o1 o& Y' L+ h* uimport pandas as pd
    2 Z4 p( C" n; x* o4 h+ ~
    + t4 m, `+ o- F" `% C( \6 H; ks = pd.Series(['abcd', 'efg', 'hi'])# r9 M: f3 p& F. b
    s.str[0]1 _, |; G/ C' W
    1
    8 C4 [9 x8 v8 W7 D6 d3 m9 F, Y' L, U! z2& i' ]( l' V; q4 B( b* \0 {
    3
    ) Z" C- ^1 [, {* G: ]: |3 G+ O46 v' l" X/ I6 g  }9 P1 r
    51 @0 r" ]' c/ ~* w$ M2 s: v* A
    0    a0 T* L. c& i" Y5 P0 j8 r$ u
    1    e" F7 O  v+ s: X5 O
    2    h
    7 P0 C; I1 s7 @- `1 u# kdtype: object# E. h. ^' E( r( S4 R
    13 R) i; g9 ?0 y# @
    2
    / D8 T# w. N/ j0 x2 i8 z2 E3
    " Z* d: d' M  ]5 T3 P  d7 A, D. G4/ _+ {0 S6 q5 H9 Y$ S
    8.1.3 string类型1 q) {8 G9 H8 v8 |% H
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。1 J5 p9 u+ E7 P! f* t; R5 w
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    6 K# w: Y1 r6 D% a# y
    ' v9 X/ j( T7 m" g二者对于某些对象的 str 序列化方法不同。) k/ j- M& ^& c" R1 d: h6 T3 M
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
    5 @& x9 b  Y( h8 ?4 q5 _/ Ds = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    & x. n8 a0 l+ @7 m; As
    # M" Z7 J) a; @8 {& y  u1
    & V" I$ B. i. Z, }; |2
    . N; W- M5 D7 t$ C2 V1 V" h2 L0    {1: 'temp_1', 2: 'temp_2'}! ]! M, _  Y: b  ^& Q) [0 w0 H5 g
    1                        [a, b]- Z3 w% I7 u  |
    2                           0.5
    ) K. L+ c  i7 e3                     my_string
      a/ E0 b8 t& m6 A8 U, I, qdtype: object& @# w: F% }5 N$ B* s4 E
    1
    # Q1 z0 k1 Z! U( V% _7 g8 p; y2
    ) L+ R! y  \# d2 p9 T% [  M3
    9 a( O+ \0 q5 t( i$ v6 `  \' h4
    : Y5 S' y/ k5 G3 u# X4 c53 Y# H2 U$ s9 k9 @. |6 L0 Y: g: a7 i
    s.str[1] # 对每个元素取[1]的操作
    3 Y( ~3 u" y% \3 \' d/ e1
    , J4 z5 v, _' q( a. _+ P- o4 e6 x0    temp_1
    8 P: U9 z3 V& D. y4 _& t1         b
    # K+ e$ j5 E* z) e2       NaN
    " I9 {) y& M& s% w' g6 k3         y: q5 \$ \3 t/ D, E# o
    dtype: object
    % q/ N) f! |6 ~# X) s8 U1
    6 [& z; M4 G8 F- l( `. g2/ A2 O2 |0 a4 `& V' ]
    3/ }' Q0 C: w8 ?( k3 G1 T- {/ t' w/ O
    4* M" W% P# w4 {+ z/ j/ Y
    59 X$ w% m5 e% N+ E
    s.astype('string').str[1]1 Q  d9 e5 Y8 C9 p
    1
    ) l9 X. J8 X* n5 w0    1' r* S* V! {% J- r# C. ~& ?
    1    '
    ' Y" }$ b; q8 O) N* s3 Y2    .
    2 q4 l7 @( Z' U3    y0 a% X  b0 P6 P" ~9 e
    dtype: string4 V3 [% Q! V3 w* J/ k6 B" @5 a5 n; }
    1% |5 v& w4 {6 B3 K; }5 ?- e9 \& {) d: q
    29 q% |! Y8 M0 Y- v' Z
    30 w4 A  l  Z. Q: C5 [$ e
    4
    + d( s+ }6 K' }5$ v% s( F% ?) x) p1 H) _! L
    除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    9 t( A, f/ I; ]+ r4 j# P, N' \% d- K! u2 ^; _  N
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    / A. G, p' j9 D) d. kstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。; M/ o* t* ~5 s  }* G0 E# ~
    string 类型是 Nullable 类型,但 object 不是
    2 }+ H) W  L8 w" G  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    % t% t- z4 z! k: O  v  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
    ; X" |3 X: A8 u$ d0 v1 n- X9 xs = pd.Series(['a'])* Q/ E6 I+ [% ^( @

    " p: r. C1 I6 S1 F7 H1 bs.str.len()$ T! G' N, l* m( |* ~4 e# a
    Out[17]:
    3 j( x2 M5 Y* D. N) z0    1
    . ]1 r. `/ u0 T1 x9 e' F: Y# {8 ydtype: int64, ^6 M9 e+ n9 p5 N$ l, L
    ! j" V# v5 T. ?
    s.astype('string').str.len()
    " G0 F! b  o0 B( V1 ?Out[18]:
    % G* |8 u9 D$ p% a1 w# q( K/ _0    1
    ! b5 X5 ]8 Q  T% K: g6 Zdtype: Int64+ x* J3 M3 G$ l, Y
    5 t; x- T2 Y$ _" }2 t& X
    s == 'a'
    + M  a2 Y3 _, w0 p7 I8 j6 B, ]Out[19]:
    5 |, Q! q, S' T" T( Z0    True) ]; \3 Z- k' S8 _0 X, N
    dtype: bool# q4 ^+ ^# O8 r" X4 R' W( `

    # r& V0 {2 D9 Y  Z& ~; r1 us.astype('string') == 'a'
    6 v# j, R  T3 P( ]0 C/ Q! f. {Out[20]: & g7 |6 ^9 L' f/ f5 i. Z. J- `4 m
    0    True& R) l- S3 I8 l
    dtype: boolean, F, n; Q/ e0 t0 d* S
    " S% v0 C1 m1 d9 P2 w: z
    s = pd.Series(['a', np.nan]) # 带有缺失值" o+ H/ o2 i$ @5 E+ P+ b# |2 V

    6 _- p: e' ?4 J2 f. js.str.len()
    ; s7 {# Y( S) d' s3 i$ n: |5 xOut[22]:
    8 p3 i4 c3 N. c( p3 c& t" n0    1.0- X7 F* ^1 C6 q5 B" X4 R* Y$ z
    1    NaN
    , d1 w- W. L) X& P6 ?' U9 ]dtype: float64
    9 R; Q. j2 u3 J7 ?; q; }5 l0 B( u' x- ^/ ~  S0 o
    s.astype('string').str.len()
    & ]9 `" p( n+ U/ MOut[23]:
    % N1 ~% T# V( i1 h  k0       10 b+ X# G. m! y3 Z( p1 l5 @; U
    1    <NA>
    1 t$ W; X* O2 i6 s9 `( V6 |; \dtype: Int64
    & k. H- j) Y( A, c5 X: Q# L# ^1 A9 W3 ?* P# U' P' ]# ]* Y; E
    s == 'a'4 i/ G! t+ O( ]7 [7 m
    Out[24]: * g# k3 q! R: \" k' E
    0     True
    ; ]3 U5 Y, E6 ~7 [6 I! B/ Y* i1    False
    % L; \1 }+ V, S: t; ~8 L' @dtype: bool5 {* h1 O  i5 \" z7 O" p% q7 \" F
    1 ~1 n, J0 x; j! o1 {
    s.astype('string') == 'a'
    ' b# ^( S  \4 i( ~1 ]7 d8 ^4 n- l. OOut[25]: - U: c$ e9 P5 z3 F. ?9 c
    0    True+ g& U) B. q/ H+ w% x
    1    <NA>
    . g" [& q, h+ [- {+ Q# O, |dtype: boolean
    . c; G8 ]: n& g! \" H4 C
    : K, r& u4 G5 B. j. P1
    2 o& U4 q2 \" V, O2 R0 L24 u5 |& r$ t& M: K0 s/ s8 X
    3
    ) t( x) @( L  N  R5 m& e. ?4
    " I* x$ P* t, K& L, x4 L5
      i  z7 h! m9 m6; A3 I8 R* }- p5 t, J4 `( o7 {
    74 B" t$ c- |. X1 F, X2 z) e. m
    85 x, n7 b/ L' ?
    9
    3 A2 T5 U) X* R+ C* q: g: J6 {( t9 Z10. A; W3 S0 d7 e. A
    11
    $ l' |2 Q: m6 Q! C( l* ?3 c. C12
    ! v# ]7 c/ h2 m6 ?8 \! d& ]13* k- S* `7 c) k6 l* L1 N- s
    146 s# J- W. x8 P' _; x' N6 }: A9 d* ]
    15
    4 L/ z+ f# ~. k& g% Z( O16
    % B* E" y8 c& C+ t) n17
    # ?" U. a0 |" [! h18- u$ F/ J8 S" ~9 @' F
    19
    2 |9 \' E) H3 [5 i- s3 o  o20
    5 Y( q1 ]% l! u, t3 l; r1 P$ [. Q1 T6 I21
    5 {' [4 ^6 ?# E  W22
    ' ~  h: C; X: y/ F! J" j23' H$ N- N3 p; K# x5 w
    240 N" {5 Z3 }6 V8 v  a$ L
    25
    - D( A' _; T) r4 ?5 F, B8 ^  q26
    7 p% o$ O/ y( A6 g4 r9 z) D27, l4 y, }# j. M& _+ M
    28) k( x5 U4 ?: H2 v- r% x
    29: _( a0 f1 d: z! L. p
    30% a# i. o6 J5 a7 k# K- `2 L$ Y
    31$ Z! M4 J& L: c9 u/ e
    32
    ) y' b9 `+ ?1 O  C338 E: @3 i% w8 `1 p4 G1 S
    34& V3 |) ]4 T2 n3 m) u0 H
    35$ @9 h/ ~0 k! d0 x! _
    36
    8 }0 D4 E) V! S1 J37" L/ D' d9 o8 Q5 E3 c, h6 ^, Y
    381 m$ X3 C  ]& F6 ]
    39
    / M1 H# o: y/ e8 _  H; t40
    . r4 u; v, ^1 Y; _+ k8 Y0 i41
    $ [/ U9 D5 R$ l7 P$ ~% N; R42
    - ?$ ^4 s( a0 B& _* {2 B* o43! Y5 t; R) J: ]0 r2 Z. ~
    44
    2 `2 Y2 G0 k9 I% r& B- N2 {45
    " P, T& e$ X2 W8 T46
    " a; D2 [; Z1 L5 M1 c47
    0 @* D: b% a8 g4 c  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
      v% l$ d/ k" y3 g4 m/ E& z
    ( Y% d. ~& B' G( qs = pd.Series([12, 345, 6789])
    5 Q* T/ O2 t. g1 g3 E4 ~9 @" ^" q/ M4 f! n( W" h. s8 e
    s.astype('string').str[1]' L& x; S( R1 m( b3 g( G
    Out[27]:   P' A8 L4 [3 r' c
    0    2
    4 w) l0 W7 i$ R2 m) m1    4" ~. c& H9 F1 [8 W+ ^2 p( V
    2    7
    - x2 u! J$ E9 _dtype: string
      |" ?. d" K+ [/ P1+ G; R; |0 G$ b8 t& h, o% a
    2  T& k( h: [  w( z( d) _" T
    3
    0 y) o7 P/ K0 _; ?( o42 ~3 }  M, m8 n- g; U; \
    51 J8 f( d3 d: h. K, T8 w
    6+ V& Q4 T8 {7 ~- Y
    71 e: k. }, N$ g0 S1 K, U5 d4 s
    8% m' B$ P# w' A! `) r$ F
    8.2 正则表达式基础" I8 g* H# K! ~- C1 F* A* J
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书$ r! C& r  F( i: Q5 ~0 ]
    2 e3 {' w- ?6 P" h2 W
    8.2.1 . 一般字符的匹配6 O0 h2 t7 @, G3 j) e
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    : l8 {; G* Y+ U% i/ t9 {. a  n$ W' ^) i1 D7 u) J: p! L$ B# D' j
    import re
    / V. D4 |- z" _% l
    7 E! O- R# S( |$ Wre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配! g$ q' ~: ?4 G: A
    Out[29]: ['Apple', 'Apple']
    / Q0 o: |% e" @- d0 s8 d0 S; D5 F1
    ' y, o; K9 n& [) {2
    2 x6 Q; A) V4 f9 y& Y5 B3; V: _' x: l9 a1 d8 b1 c7 M
    4
    * F( ]/ j6 P5 W. E9 b' w6 v! e8.2.2 元字符基础
    ) }1 }% e1 F5 S$ I  s元字符        描述
      W6 g1 w$ F2 Y( z* z.        匹配除换行符以外的任意字符# q0 [5 h  n/ \: s
    [ ]        字符类,匹配方括号中包含的任意字符3 K( j9 ]) E! ?9 U' h
    [^ ]        否定字符类,匹配方括号中不包含的任意字符  M8 V; x0 ]; B
    *        匹配前面的子表达式零次或多次: `$ i& ~' t8 i7 U, x0 N3 ~2 I4 T& D
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
      Q! _0 }/ M# ?4 w1 Y+ f?        匹配前面的子表达式零次或一次,非贪婪方式. k9 g1 y, Y0 U
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式8 i6 z1 m; C) G5 Q: M& z5 G4 h" h
    (xyz)        字符组,按照确切的顺序匹配字符xyz. @6 Q3 A; Z+ y+ D( A7 Z3 k" ]
    |        分支结构,匹配符号之前的字符或后面的字符; f8 a9 w  @  ]+ ]+ G
    \        转义符,它可以还原元字符原来的含义1 l7 \* N/ t% n, Q+ u% v0 ?
    ^        匹配行的开始$ a. x# o: a# d4 }3 T
    $        匹配行的结束' T) f4 v. ^/ z6 Y# W4 p
    import re
    4 H* ^, k+ D! _* D$ F/ t9 Ure.findall(r'.', 'abc')* @( Z& Q* s+ |1 ~2 C( R( n4 X# q
    Out[30]: ['a', 'b', 'c']  K- }3 V& ]0 G/ [' {3 q. {
    2 D& c4 v) X4 U$ i3 L
    re.findall(r'[ac]', 'abc') # []中有的子串都匹配4 c% \9 ?' ?) |- L/ i
    Out[31]: ['a', 'c']
    5 u' h% l% J' s
    3 B$ J+ [6 p- Z. ~! _* n* I3 c- |re.findall(r'[^ac]', 'abc') 1 l+ F; W# F& }8 O% E
    Out[32]: ['b']
    * }9 I; W6 V$ r2 r
    $ m/ N1 F  E5 Y, t' [' Z4 sre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次+ z6 J0 g7 D1 P/ f
    Out[33]: ['aa', 'aa', 'bb', 'bb']6 j. N/ z8 M2 _9 n) _

    % X. V, r2 M* ~4 f% M; ]re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串2 L3 A, L$ \" z/ O( E+ C- O
    Out[34]: ['ca', 'bbc', 'bbc']
    7 O. E& k' H7 \, k  z6 t  u8 \) s/ O$ ?) ]5 e( k
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。+ I% v: ^  @; Z3 H4 F
    """& n; Z* l. [$ ^4 [
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
    3 q6 r8 o: O/ ?- v$ s2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
    ; S/ J/ r4 g6 y2 F' \3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
    9 ?* M- \7 F2 ?5 x' E* T8 v& k) a0 T但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    3 E( r. h  K  H"""
    / p0 v# _; _) r5 d: E
    : r2 {7 L+ U# ]re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
    ' H9 f0 N, S/ z" |2 W& TOut[35]: ['a', 'a', 'a', 'a'], L1 Z! W+ Z8 z: w* q& @; W

    8 r  x, Q( W! m) a" d8 k5 E! @# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    6 n3 c7 I  P# V( P- S+ d' y# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。2 ]+ t/ g# \8 ]* G8 j
    re.findall(r'\\', 'aa\a*a')
    0 `9 V8 Q& n4 \. W6 \3 T& p4 c[]
    9 n+ C$ f9 M% C8 C
    & Q4 ~: F, e$ e0 X2 Y1 k- Lre.findall(r'a?.', 'abaacadaae')
    + y" f) _, |$ L( v- C9 oOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    6 ^0 @; Z$ Q1 f, o- q0 T- A8 \5 N9 b! q; M1 }
    re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表8 L$ l: _7 }) O, y# p0 t2 c0 M
    [('width', '20'), ('height', '10')]
    - T6 ~0 q- R1 P9 M9 y/ w1 v
    8 r( @! }5 s4 L# f14 ~# W7 P" c( Y. e/ p
    2
    0 Z0 v& Y( C1 X0 L3. n1 |2 n" q7 M" [
    4& f+ S) D: c( t) w$ ^( x8 U5 c
    5% Z. v  ?; v% j4 ?9 f
    6' N5 N# C5 ~+ N! B+ ?! [6 F
    7/ j9 m( S! M' \  g# C
    8
    * s% i* _! a; ]7 l7 O93 d$ q+ y: ?+ E9 c6 `6 _1 p
    108 h) [/ u2 b! r0 d0 c- L# E
    11* y6 z& m- r/ }3 Y! v3 k
    12
    # y5 ?! o) g$ f8 n( e13
    7 w# y, K( E9 ]( S& s) w% ]14
    ' z, L) F" r4 q, p6 [15. E& v' j# _$ Z' D2 D; p0 m1 ~( t
    16
    6 P( ]/ P8 y( m7 n6 k179 n4 H/ H8 T0 u3 `" S* I
    18
    3 [$ {9 W- q4 L" t2 n; T19
    1 A+ {9 b+ E5 K0 }, s/ F20
    6 C6 c/ m" Y% Z8 y: @* n; A9 K' v- ?9 l) k21# \6 A7 @2 X# i; ~1 a6 I' v; K
    22
    - O- d0 `& U5 T2 B/ {, {" T, X23  n2 e4 s; O+ m- M6 r. O- M
    24
    ' k8 X$ J0 P3 H. ?  q4 }$ d. ^' @& M25
    % c# P% I9 D2 m' f) t8 j260 z$ U0 ?7 q/ j! V6 q# F* D. `1 B/ X: y
    27
    , V+ N) `3 A; |0 R' c7 s+ r28& H8 v5 }) v8 t: @
    29
    4 F+ E7 e: J1 j5 C/ c. r30
    % L; d3 T* p2 Y$ ~- ~+ L31
    9 {% b2 ]3 z6 ^$ k. v# _325 W* }5 j$ |3 m/ L" C
    33
    5 c, D. R  J: [2 s34
    $ n5 e( T9 v/ v2 r3 \7 f35. K# A7 L1 f5 Z7 h9 J
    36+ e9 X+ h1 C+ l) S, Y
    37
    ; w. w2 y- A+ f& d. H! @3 A8.2.3 简写字符集
    / ]5 }0 {5 ?6 T1 p( w则表达式中还有一类简写字符集,其等价于一组字符的集合:
    % s4 V: Q% i; s) z0 L  k; J' }! a% j5 W  t  u6 N6 E  L
    简写        描述
    9 ]* K9 L9 V1 z\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    8 e  @5 k: B6 h7 X7 ]\W        匹配非字母和数字的字符: [^\w]0 H3 C- p) z% |6 J& m) g5 G' F  }
    \d        匹配数字: [0-9]
    * D# t' M  h3 ~+ N\D        匹配非数字: [^\d]
    % D: z3 ?: o9 H4 a\s        匹配空格符: [\t\n\f\r\p{Z}]) ^. \0 Z! Y2 V! X) s7 y' e5 l1 }
    \S        匹配非空格符: [^\s]1 [1 z1 A8 {7 a& G; N
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    0 ?$ `( ]7 k3 l5 b" lre.findall(r'.s', 'Apple! This Is an Apple!')
    6 X" y! s+ M/ q) L8 xOut[37]: ['is', 'Is']/ v3 i9 ?' J# y
    1 ^' M5 H, R3 B: L+ x
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
    , ]5 l6 c$ S% A) c" V7 Z$ Z6 pOut[38]: ['09', '7w', 'c_', '9q']
    * r1 R  Y9 `. s  [4 p- N1 D! m) E+ T' }, {7 Q8 N/ u- G: [. [, y
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)& x( x- G0 X& {  C7 e  @
    Out[39]: ['8?', 'p@']+ Y" J  d' i3 \
    : A: F- d& C" o# }" M
    re.findall(r'.\s.', 'Constant dropping wears the stone.'). A- Z  d- Y' |6 \3 `. q
    Out[40]: ['t d', 'g w', 's t', 'e s']0 t$ K0 |9 S( ^" R+ w' i4 Q
      U8 k( W2 D9 Q; t6 y
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',: `- z4 V7 r; z! Y+ v% a2 G
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号'), Q$ _) h& Z8 \- D2 ^
    : C' p" I, ]3 ?0 _
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]3 x" w% b+ V* P% q' m7 h

    * U! D. G# v( U$ d, y( t1
    / r7 d" p/ a$ b1 W2* U* X5 x4 `3 n) n$ _* `
    3& C' f0 I, w1 K
    44 X: [3 ]( f! x5 f
    52 E7 N3 r5 X/ n3 F3 t3 m
    6
    : O! H0 h' {  u1 H: H% {73 o0 |7 H0 `* X3 ]' j& D
    8
    " b; c: \' Q6 ?$ _& Y- S# ~( ~99 a/ ], A% O! ~1 Q6 T) X
    10( a! K5 `# Q/ \6 h4 @, q8 k; `
    11- n- j# I6 g5 E, S0 u
    12
    " ]# z: q, {% _1 p* j13# A  q! \9 ~" q# a/ G" M
    14  z: L% Q, L3 w0 C
    15
    3 r# B& o  \3 d$ n8 k# G6 D16
    0 \! r1 R1 J3 f# n" n3 U% X8.3 文本处理的五类操作
    6 K6 o/ c( g: e3 m( ]8.3.1 str.split 拆分
    / \8 i' ?3 C$ b! d- g2 u  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    : ^$ S4 f% A, s2 j3 z5 T; |3 V' ~' g3 m- c
    s = pd.Series(['上海市黄浦区方浜中路249号',$ X( }8 n/ s" n! R9 w/ i
                '上海市宝山区密山路5号'])/ V$ o$ e" [( I

    & Q! W% G4 [3 H* I, ]7 c: f
    ' Z0 R% u; P" D, I1 a, s( Es.str.split('[市区路]') # 每条结果为一行,相当于Series
    - ~- |* A- w( t- ]  A/ ZOut[43]:
    , R5 W" x: ?& G0    [上海, 黄浦, 方浜中, 249号]
    ; J0 S/ ~# [! i3 j1       [上海, 宝山, 密山, 5号]& s' C# _. ^' d6 n% R
    dtype: object: @5 ]6 Q- ?4 h+ A! a* G0 c

    7 Z& f# B( e4 J* l3 Ws.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    7 k* Y- Q: n+ E( h  m/ n: r( U2 MOut[44]: * L2 a7 h+ v& p, H: b
        0   1         24 T- n3 B, }/ Z9 Y
    0  上海  黄浦  方浜中路249号
    ' E2 P& a- S( v1 O4 r1  上海  宝山     密山路5号/ Z4 w- Y9 r. O8 x- i9 u& H
    1
    + ]% H7 P$ o! l% X) P5 y3 C2
    1 e4 E2 q% z% V' H) i( f3 ~3
    ; D/ |+ Q! P  x. [" E  L  M" N3 B4
    # l6 g4 I, o/ k- B) d7 ?/ e5' v1 D! ?9 k. \. B0 u; p& F6 @6 t5 o
    6# K7 E) i: I1 J" B- \' _  a
    75 \" }9 S1 E/ Y0 m. G: U
    8
    9 Y' S1 s  W' ?9! {2 g9 S3 ^1 e" Y# e
    10
    + T) h' E8 B9 E7 e4 V7 B11: a+ K4 t% z- P0 V8 |) L, v- O
    12
    ; S* Z( Z* J- M: M  k134 O3 e! ^0 `9 Z: c8 P6 I/ p; q
    148 r4 I+ z2 N  [4 A# M8 G
    15
    " G# k! O$ X% Z; X  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
    ' W  k8 [2 c7 _9 k) s/ L/ }' ?# d( l1 I. A1 h2 f9 G6 L* _
    s.str.rsplit('[市区路]', n=2, expand=True)8 v3 [4 W( O2 q8 M: A1 m
    Out[45]:
    ) X; a7 {9 Z- i3 b9 z                0; l6 L% F6 V; B# U* Y; d
    0  上海市黄浦区方浜中路249号
    ) E; V. r9 }$ ?% j1     上海市宝山区密山路5号1 k- M0 i# A4 D, U: m, }1 ~  t4 t
    13 T3 a' s. l: P
    26 E3 r. p6 B6 n5 o& D9 c
    3
    6 y1 M9 U* S( L* {# U" x0 ?4
    ' ?* H( W  H& d( M5
    1 O+ P9 Z. T% n' K9 Z8 p8.3.2 str.join 或 str.cat 合并' f3 U/ L  p+ u
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    3 Z) ?8 u! v3 c# ]str.cat 用于合并两个序列,主要参数为:7 G. a9 N& A# }5 C1 K
    sep:连接符、
    ' l) m+ U! W$ g  R4 q2 mjoin:连接形式默认为以索引为键的左连接
    . ~: `7 i. L9 L, Y7 `na_rep:缺失值替代符号
    + ]9 z% f/ B/ |& A. Js = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])9 u& _, v4 J( o
    s.str.join('-')2 V8 [+ n  o8 K9 A6 U+ ?% r
    Out[47]: ; E5 |$ P. O* |7 z0 S
    0    a-b; z( k0 j0 V) n; \3 v* y( A- l
    1    NaN7 D/ f  F$ f% N3 u( I# `
    2    NaN7 W- q, j, U1 N7 d: L& W; |
    dtype: object7 ?: r2 P- J4 `" \/ l% r
    1/ V- q$ }0 n3 D/ Q
    2
    1 G, \& }6 H. g# X  }7 m/ k4 Z3
    1 i  @$ Y% E# L4 ]& m+ u- ]' F3 F' d4
    7 S( N- w+ y1 V9 ~) C$ H& X53 Y! g8 L  y: ^$ Z& v  [" o
    6
    1 n6 A- e; h/ Z' `) o7
    ! E- I. u. u7 K) D9 E- us1 = pd.Series(['a','b']); O- n1 _! l8 {( x9 d9 b1 Z
    s2 = pd.Series(['cat','dog'])2 x2 k3 g4 D6 p, \' ^8 ?
    s1.str.cat(s2,sep='-')* X6 W: M: w$ K' `
    Out[50]: 2 ?* }9 e. W3 H
    0    a-cat
    0 w- Q. d: r% P1    b-dog
      g& e6 O! Q7 L. n' _* }. `" ddtype: object, ]+ G* h* d& a3 S( c$ L" {; i- F

    4 Z- M# k# V4 k9 Y# w  e6 b$ rs2.index = [1, 2]- l. d. K& j- V/ ^/ g
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')) ?( j  H$ O5 f7 }2 e: w! ]
    Out[52]: ) u0 W! {3 p7 q! W+ D; [
    0      a-?4 @! `7 T, I: b# D' W6 E- ?
    1    b-cat
    2 M" X0 P" u: F4 n9 ~2    ?-dog
    / }5 h  X- @9 z. T1 d, zdtype: object
    5 d- ]6 k, D: X  d# ^! F' a9 H& e17 C  M5 F; U; a6 Q* F. i1 m( K, h* u
    2
    1 Z- m) k# H* e0 |4 m3! r  S4 u) V, i3 e/ v) A( K5 o$ n
    4
    , w& o$ T, \* X52 e* Y7 H* v, G* e& g. _+ N
    6
    / k: p! _$ I$ ^) m7 i+ `7
    - w# \& F- T; u5 ~86 {5 R: U$ l! {6 g! K+ ~( U- z
    9! r9 M* Q  U& F
    106 ~+ X4 f4 P5 z
    112 c0 Z' p. ~7 H/ L0 ?6 {! q  e
    12* t; U. n! B3 o1 l$ T
    136 D& W; [; E- y$ L! s' Q  K
    14) H! o, W; B2 e
    15$ x& g* n# f2 @3 }* D: ^4 \
    8.3.3 匹配
    3 ]: I: h' a( a+ n9 Istr.contains返回了每个字符串是否包含正则模式的布尔序列:
    ' m3 Y. E% t5 c1 b  e9 ys = pd.Series(['my cat', 'he is fat', 'railway station'])
    + R8 U6 P# Z* T+ {s.str.contains('\s\wat')
    3 O: z5 N7 A# Z$ a( p
    % u3 G( `# G9 a/ \  v0     True/ M, X* k- }+ m9 B- s, Z- i  ~
    1     True) p& O- Y5 r- x% w0 I% S+ v
    2    False1 P! n4 O9 l: ?# K
    dtype: bool
    # b& ?! h. q6 H* T1
    ) `3 a- b- z' x27 h+ j" n  R! J  E) ^" W: O2 H
    3. L( l5 S1 Y9 h) B: z$ s
    4$ \9 D9 U& {$ _7 y" C8 `# P+ |
    5
    & d& {0 L4 x$ ~6" Z# i. X) ?+ F$ }9 z: o
    7( J2 {) W2 F2 z6 O& ~
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:) l2 P& v$ g% y0 M8 I
    s.str.startswith('my')0 ]" n1 c4 j* m- R- x% d/ t% w

    , @( K( G3 E" \6 }+ m' ?, Y0     True! Z" m- J) U) h( L7 l" I
    1    False. }5 [6 H* @' m' N$ n! m
    2    False
    - a! O9 [) m) p7 c5 W4 Pdtype: bool
    7 Q  ?; C; v; O- h. F* ], @3 f1
    ' V$ x9 T" `& j0 }2! q& L( u) I; b  `  c6 F3 |
    3
    5 q; ]' t! X9 ^9 I2 w4
    2 v; u" z) \* y' o* a7 M/ Z4 E0 `5" N4 b4 X9 @$ j. k0 C
    61 y1 K: ?& w/ m3 G
    s.str.endswith('t')& [# b6 i& N, e  `, P3 Z+ H7 h; i

    8 l- r1 M/ g& b; \$ s! W6 m0     True* t! _- u( v" O5 u  ~; ~
    1     True. r2 F2 p9 S0 c$ W5 Y' B$ N
    2    False$ f( k- C7 A+ r; [" q& }% `8 C
    dtype: bool! j0 B# C' W$ P) Q! p8 Y9 N. C
    1
    : u" x1 j9 K) A, `! G! p  o2+ y: T: D4 d! z
    3
    - Q4 j! [+ v( p0 ~. a) ~- `4/ t# z# j: Y2 k. [) K* W
    5% y- E9 U# C9 Y
    6- O' D2 V* L2 v  J8 H
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    . m% M( e* ]7 b& N: M0 f6 Ws.str.match('m|h'), v2 f7 D" |0 `6 p5 ~/ f5 a
    s.str.contains('^[m|h]') # 二者等价7 d# z7 V( A- k" `! |$ N

    1 B2 }: U- m( R, L0     True0 i( U/ v9 f* b' u# |) H
    1     True
    0 q! C2 W( Z& a9 x' P2    False
    3 _8 [; _2 `) C' |4 g! ndtype: bool' a" C& P9 Z% f( N  @
    1
    , z/ _2 ^* y& |) Z' r4 W- \$ @2& F& _  E' Q" j! m# S6 m
    3# I0 C6 E  `+ S. }/ T) }0 O0 O2 X' b2 h
    4
    2 ^& E1 z. [' Z* j59 x0 i8 D4 o3 b1 j5 }, u' i9 N; C
    6
    $ K: X5 f+ w$ x% o; O. [7# t+ \$ Z4 o& \7 T  i
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配% y, \7 C- A6 C/ t9 @9 ?( j
    s.str.contains('[f|g]at|n$')       # 二者等价4 G+ m  }/ v* M: z7 y7 e2 e! T  Q

    " N' ~1 E8 d) k8 `0    False% J4 u4 ?; X) h2 T1 K$ ?' [& g) P* Q& _
    1     True
    ' ~# b0 n5 ~. p! N9 c& U2     True
    6 Y9 Z* n& D0 e% l+ {dtype: bool$ D" {/ d! k2 R. Q, e
    1, r7 o$ C( H7 Z
    2
    * }4 i) z) {3 T/ B6 F1 r3 y31 O! d9 Y4 ^/ B8 u2 C
    4
    6 X9 a. y' B. B9 C- ?$ Q5. h+ s3 ~( X! H' E5 k
    6
    * j) z3 q( _8 V7( n# @9 b4 B; O9 c
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:+ K  @! g  o6 F  y; F9 S. Z
    s = pd.Series(['This is an apple. That is not an apple.'])( I: d( v& {8 \# o

    ' @* r# c- U/ B4 S" w) Ss.str.find('apple')
    3 P. K' b3 @9 q0 v8 dOut[62]:
    3 v$ v6 ?" G9 E8 n$ K5 I, j0    11
      B2 e7 k# j1 b! t. jdtype: int64
    6 o' Y! ?* Q/ C- ^. s' y
    7 n$ E* {- K  B. `, P' hs.str.rfind('apple'): N  z6 R; x/ @! j! a1 k9 \
    Out[63]: 7 ^$ U2 q" @! l; U
    0    33
      k. y8 j; L# A) C* H& S7 adtype: int64
    ; L/ U  Y2 D" f5 J' I! O10 x. s/ h- D6 e+ t' |1 |1 z
    2  @2 u1 A. q3 D  o+ {+ g
    3" N+ P* W9 \. B& [1 k' o
    4
    8 K1 s: K$ f4 z54 M) J8 a8 Z: ^/ |  j
    6% D; N  z; X2 F- R" I% z
    70 C8 ^0 J" I* U6 b' w
    8  y& L$ L! [4 @) g6 E/ i6 _
    9
    / }: }$ ]3 F, T3 y. q. E" j+ s/ J10
    ) N! ]3 G* j8 W+ i4 ]11
    0 ]! T( G% O% J4 A* D替换& z' l& p1 R# V) ^/ [! u  h: \
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    / r% ?8 @4 @3 n. e; i4 [" Fs = pd.Series(['a_1_b','c_?'])
    4 T& l7 s/ H( U8 ^' t7 S" q# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    " G' q: {9 c' cs.str.replace('\d|\?', 'new', regex=True) " b7 a& d+ j* K) {$ h! P8 G) `+ H& E
    9 B0 U1 Q, e; {2 D8 A
    0    a_new_b
    1 }# A: k2 r4 S' C1      c_new& F3 x. J$ j9 a0 v- F  A$ Q
    dtype: object
    4 g& ~! r) Q  g# |4 S  C7 u10 y. b" x1 H2 H4 N7 @
    2
    + R. ~  z, d# u+ U/ ^6 b3 Q# N0 @+ F; [/ N3, w7 i/ g$ F, J4 g# C" s' M1 c
    4& ^4 E" g7 p6 D
    59 B7 a6 k! f  ?9 p9 W/ K5 \  M
    6
    ( t; A9 a" C( I3 P74 Z7 g1 n1 N" O
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    ' j* ^4 z( i' u. Q5 z1 ?2 B* I1 E* O3 R% N  }( b; r- C' |
    s = pd.Series(['上海市黄浦区方浜中路249号',
    0 k7 \. x3 m% l9 r. S, v                '上海市宝山区密山路5号',
    : w+ H, K! Q! ]. ^/ m                '北京市昌平区北农路2号'])1 y" r% F- X. l  b" T$ V8 \, Y# l' ?
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'5 S3 ?& t& x- @% |
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    * L( Q' ]+ S  s3 @5 N. A2 i) `district = {'昌平区': 'CP District',
    + M  Z/ I2 u, i( {# k            '黄浦区': 'HP District',
    ! m/ W$ M' U7 m1 c; s5 W( o$ i% B            '宝山区': 'BS District'}. S/ V% s) |( v. {
    road = {'方浜中路': 'Mid Fangbin Road',
    ! Y4 I" C5 S& O* A8 E        '密山路': 'Mishan Road',
    0 M7 k. T! M/ x; t        '北农路': 'Beinong Road'}
    ' I+ H4 D% M% ^6 `8 w; ldef my_func(m):
      i. k0 b9 H4 {  Z( m7 Y$ G    str_city = city[m.group(1)]
    . w% F9 _& W6 S/ w" a. T+ W$ T    str_district = district[m.group(2)]
    3 Q: P$ j% g0 f: R    str_road = road[m.group(3)]% H, i) O4 s1 J2 k2 ^+ t4 _1 z& l5 w
        str_no = 'No. ' + m.group(4)[:-1]" w! b: v7 U/ }/ X  J, M
        return ' '.join([str_city,
    ! ~& k9 a7 a1 ~4 G                     str_district," a0 q( F# P6 y; c
                         str_road,1 X: Z$ d# D; \* @; O
                         str_no])
    ( e7 Y# I& e7 @s.str.replace(pat, my_func, regex=True): X( S. U' c! d/ D) h
    ! q+ N7 r8 i2 N2 J
    1
      b, Z% w( s( s* C2
    4 A1 g2 X0 l; I3
    0 v) _0 o; I, z: @8 F, u' e$ [5 e! N4 `" y4
      g# a; Q; j0 W51 h0 N2 |0 K/ O6 P
    6# w* `: p9 w$ D/ E! ^
    7
    / |. @/ j! E# P1 e$ @: a4 [+ V8$ |1 V) w: T- B
    9* F1 {  |0 e5 {9 n- h; Y
    10
    9 O) A# `1 B8 R) v+ r3 S9 ~- I- U11
    , Q( v0 t4 \4 [12) x3 D. f) D, w5 Q# L" t; M. Q) T
    13
    ( a2 \8 k; k7 E& V# G, |14' |! E5 j! B4 S' L
    158 e7 b2 u) H, k3 m' z( f+ P
    16
    8 o; @3 N' @' c: u# u178 B, l* `/ N/ p
    18
    1 g# u2 T6 S% M. A8 O19
    . l+ L% b) s0 q* g207 J% b6 ?1 T$ g
    218 j0 y0 C$ m. |8 V& m8 |
    0    Shanghai HP District Mid Fangbin Road No. 249
    & R+ ^2 v3 H- q4 r" x1           Shanghai BS District Mishan Road No. 5
    ) I5 u" i) N$ Q  h, |/ p2 u2           Beijing CP District Beinong Road No. 2
    $ r7 N0 x/ Q' L, p7 v* @dtype: object
    + @$ n- D4 M, u; u1 P' X1+ A% G/ z! d# O1 v9 M8 A6 X* [; m
    22 ]  |! Z$ c/ R: z5 X3 {- h
    3# I: Q5 m- E8 y$ ~
    41 g# Y  J9 [# z7 D
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:2 l! C6 {) n6 _+ z& G0 s$ |8 d
    : H$ q- e- x9 L. J
    # 将各个子组进行命名- i# _5 A, Y5 }' m
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'- C5 _$ z: ?! ?) _& b! L
    def my_func(m):: w# c# T. j* @; v# R
        str_city = city[m.group('市名')]
    ) z# H; V# f" p* y    str_district = district[m.group('区名')]+ a9 [2 x, t4 ^0 G" N: Q
        str_road = road[m.group('路名')]- F$ [8 J! \* a. w0 r
        str_no = 'No. ' + m.group('编号')[:-1]
    $ b) Q8 ~/ W! n2 a3 ?3 y    return ' '.join([str_city,% `  V2 ?4 X6 m& X
                         str_district,. ?$ P' C8 c( s% {& f3 F6 w
                         str_road,
    % D5 e% P0 I- @5 G+ Z# a                     str_no])8 o1 V) x* X' H  i1 f
    s.str.replace(pat, my_func, regex=True)
    / [8 N8 e1 {( t$ C1% g+ F7 V) @; Y2 D
    2
    ! I5 v/ G3 r. L+ U: x! y) k- e: h) f38 c- M$ }' U9 |- [* M  A4 J& W
    4
    - \- e- {5 r2 u) }. Z5
    ( I. Y8 Q1 K, g( ~( O% X6& ~' A1 i# v+ h' W4 _% \
    7
    0 w  X) x& T" ]. o8
    $ Z* U  d0 k) e+ y/ ~9 R* S9" n4 T/ X0 V, p. ~0 v
    10
    9 B: j8 n# H( f- X  l) N$ T11- ^9 O* _: ]$ P' D
    125 Q( D- c4 [/ W8 |4 q
    0    Shanghai HP District Mid Fangbin Road No. 249
    * g) y* K) O! l; ]% T1           Shanghai BS District Mishan Road No. 5/ T. [0 ^: T7 K
    2           Beijing CP District Beinong Road No. 2  `, ?6 |, f9 j, P6 U/ B6 q
    dtype: object
    7 k2 ^$ }4 w& o5 d2 ?1
    4 V$ A1 N" y  b3 w24 I: f3 U5 _6 V, z: N: n
    3  E3 L- k: G5 m1 P# \
    4& D7 d3 w% \; d3 I: Y
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。5 d3 D5 S- ~/ @8 L
    7 @0 q& V$ I: Y  S
    8.3.5 提取
    3 f" p( l8 ^, N% x9 ]* Z* [str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    , v- x; x, L- ^! V) Z* Ds.str.split('[市区路]')9 I! `: `; h1 O  J. P; K
    Out[43]: ' ]6 N0 A8 P- i
    0    [上海, 黄浦, 方浜中, 249号]" r. Z( I9 M0 {
    1       [上海, 宝山, 密山, 5号]6 ~. S/ U3 ?* J8 P
    dtype: object
    2 L5 s/ q! K# C% ]5 |, A, E& b: w" `: P+ `  ~
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'! Y& p7 Z# r( o6 Y9 D
    s.str.extract(pat)3 m) |, J; e1 O
    Out[78]:
    ( T! w/ a. z( w2 D4 @& e# e$ H; @. W    0    1     2     30 t. g& ?+ u' ~3 ]$ H) O7 Q4 S7 s2 s
    0  上海市  黄浦区  方浜中路  249号8 @1 Y- _& R, o4 r& O6 y. m
    1  上海市  宝山区   密山路    5号+ N7 ~& P% W0 F5 Q
    2  北京市  昌平区   北农路    2号
    # b; L$ v- {" }1
    6 H: F1 ^; g9 f' p8 \! c9 L/ i2, e2 q* r/ B8 H1 N
    3
    " p$ {% B) A" V; y: o( h4 ]8 W8 j4
    6 W+ w7 H2 N' s" l+ _: _9 s7 ?0 J52 N( w3 s* y9 p; S& P* E: W
    6
    ( A3 ^' V. V, j8 q7" q8 h- R* t* K1 O' X& A
    84 I9 g$ z- k+ H2 W; U
    9
    & W& d8 \. A$ k, v; l- U10  I; F& y1 ^1 k6 x" e* y
    11) g- u/ F: b, ^8 A. A
    12
    ; O5 ~) V8 z) Y# l13
    ' G, u, C5 b/ S' d通过子组的命名,可以直接对新生成DataFrame的列命名:
      J& w2 O* I; j' E/ y% v
    6 q. E% V$ S% e) T! P- Fpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    4 v& J& G' j. h! x. n. v5 Q# Hs.str.extract(pat)
    ; q) `; B3 s% b) fOut[79]: 6 o# A- O! r. Q' x( `1 I6 ]
        市名   区名    路名    编号0 d: N8 @3 o2 l  O- |
    0  上海市  黄浦区  方浜中路  249号
    - T0 M4 W4 B( K6 i7 U8 _3 [1  上海市  宝山区   密山路    5号
    ( u# P  X4 I7 ^; B- ?2  北京市  昌平区   北农路    2号
    # G0 J% P3 ^! ^: C; t( O10 Q, e: ?8 P$ r- V! |, X
    2; {- d6 J2 `& P6 w* b7 [5 G" C
    3
    - B- v7 s. u+ f3 J# i4& @# [/ M3 e: n0 _+ L
    5) P, a7 E$ u. H* C) ~8 i0 w1 R
    6
    * I9 ^* R: e# g. O2 |7 W# [4 x7
    ( a3 ?( U' n  _: jstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:- T- ?" N- `1 S1 j  z. z+ X
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    1 |; o$ I2 `% V+ t! Y4 f# kpat = '[A|B](\d+)[T|S](\d+)'
    # @9 {" ?- r9 a3 I" Js.str.extractall(pat)2 f/ v0 X' ~7 D6 |' G2 K6 w5 ~
    Out[83]:( O# S$ o3 m9 k. W, ?. n
           0   1
    4 T8 x/ P3 h+ `' _& W0 z  U4 f; L     match         # Y3 f# b; s: Q( d
    my_A 0      135  15
    ) n. h& Z; v& e4 l6 s4 `     1       26   5
    ' v- s6 O( ?  @8 m! Q3 m* jmy_B 0      674   26 L+ R( {# N7 y% x( D
         1       25   66 R# c8 ~- w' t, r4 v% o
    1
    " E4 f* Q" ^1 o6 f# W2 p( Q2
    6 }1 m' r! T, |8 h9 g% M; X" z3
    / l& U! [0 P* S3 E8 @' A- s% m! j2 U4$ K9 Z* Z5 D& D- M6 E
    5# {6 J8 l  d! b2 N6 ~. U
    6
    # u+ o4 {* {& z* G# ]. X- q7' E. J  d2 }4 w  T  R9 k
    8* \7 [4 a- i( Z" n
    9
    2 W4 B1 O3 F* B- a: d! P# J10
    & ?" |/ F8 ]$ w' E8 y: U5 ^8 jpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    , E* E  M2 D, R! L! as.str.extractall(pat_with_name)) |- b& {  a+ v
    Out[84]:
    ! G) N- c4 K- L* p! D! h3 x           name1 name2
    9 z! q4 G# \3 J$ B) e) e     match            / F% b5 H+ g$ ^8 x8 b
    my_A 0       135    15% `% M8 X) S' e
         1        26     50 x2 L5 m& f$ {; H; g- `/ P
    my_B 0       674     2
    7 |) D; b3 R2 I, ], ]     1        25     6
    " f  w) c2 _7 e" ?0 I1 y1
    1 z5 l  s% P$ U. f# t2; G  i3 @$ B/ P/ B" w
    3
    : {! D, o- {) @4
    ! p$ @1 f2 d3 [) k( ~5
    0 f5 p0 q/ }/ k: H' f6' S: r: h/ K9 C+ b8 U6 N
    78 ?4 L& h% V1 K& U' c) D. k9 i. P
    80 K/ ?- H) X. k
    9  g$ y0 e) Q( u5 @( Z5 S9 E
    str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
      S# \3 l( |! [  w) Q/ n" Z/ d0 n! ~s.str.findall(pat)
    : s  [$ I0 p/ p3 A0 c1
    3 u/ o) I8 B( Dmy_A    [(135, 15), (26, 5)]& P+ n- V9 G) o# i6 s0 c
    my_B     [(674, 2), (25, 6)]
    2 V( C% p0 d" `9 N% t; h6 J: pdtype: object7 }* ^4 m' ~% D( ?
    1( U+ t1 u0 ?- q
    2
    - t6 c! g2 r" I2 V6 e; _3
    - B) I! x. V; L" @8 q  @8.4、常用字符串函数
    5 q- B3 c: [, b2 U  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。4 i+ p9 u, U; A, f! l
    # }" j/ g, \% z4 r) h" v0 V
    8.4.1 字母型函数1 l/ F4 A' I. N/ O, t
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:3 Z8 H$ E! C6 q& I
    & l) \% p$ T5 ?9 e* Y- @
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    % X# M0 ^5 l( P- t9 g: G! M
    " L1 i( D/ B+ \3 ^. D+ cs.str.upper()
    9 x( S' k. a: x+ {+ E; _' H# n( ~' OOut[87]:
    . j5 L* ^5 ~& H0                 LOWER* n. ~6 \3 n4 N& {% H
    1              CAPITALS" v: Q+ I- t# ^) J; x: n" P# a
    2    THIS IS A SENTENCE% [0 l+ r1 e3 q. v' M
    3              SWAPCASE
    . [/ x2 ]4 J& O  l/ |1 mdtype: object( V1 y0 g( Y, u! Z, C" r6 R  N

    $ G' _- C7 {& z8 r* V7 js.str.lower(): `  ]! R. U; y  k! X
    Out[88]:
    8 u2 p& L( x/ z' o, \0 b- h  f% w% M0 T; u0                 lower4 g2 G+ P9 U6 s/ _
    1              capitals5 p5 B* }( j8 ^9 c
    2    this is a sentence  O$ D2 g+ h# H
    3              swapcase
    7 B9 R* W$ w- B) ~' \dtype: object1 M/ F+ ]) j" [8 O; `
    : [+ G# {# }" P9 a
    s.str.title()  # 首字母大写: \) y7 k) ~% W! `4 N
    Out[89]: ) |2 R1 J" w+ v* a* c- U! W9 U+ G
    0                 Lower8 a8 o# H; i; F( O1 _
    1              Capitals$ O1 m  w0 E/ l0 c7 y- \
    2    This Is A Sentence
    ' U& ^4 y1 r0 c8 M  c# `3 v4 c& R3              Swapcase5 G% A2 o9 n/ ~' h& `' r) ^
    dtype: object' G& `& V( n; K) q

    5 O0 f* n$ E0 \- |% Y: h( v3 bs.str.capitalize()  # 句首大写
    4 s& t* Q% B3 X: FOut[90]: 6 j0 n5 f" U( g
    0                 Lower) o9 V: d: \' d4 C  o- ~7 e1 u
    1              Capitals
    5 J5 a/ h5 ?* N- K9 h2    This is a sentence
    6 P# n) V6 K& c8 h% n0 }3              Swapcase/ ]; _$ s4 E2 z
    dtype: object
    6 p9 g, ^- Y; A) Z- _0 `0 N
    6 D5 a6 ?: z7 V( ?& Qs.str.swapcase() # 将大写转换为小写,将小写转换为大写。* @/ O7 r2 S4 ~. X! x# K, e/ D  A0 G
    Out[91]: 1 L, [8 ?  T& `! |
    0                 LOWER/ L9 `. {) K; K- O- N/ I: w7 N
    1              capitals: @0 o+ W* K0 M3 b+ ~
    2    THIS IS A SENTENCE" |* c& B, E; u) y- I- T" g3 P5 B3 b
    3              sWaPcAsE
    ; b4 ], E3 h7 X% U3 g, x$ }dtype: object5 w) K1 K  H$ d7 n3 Q5 T

    , o4 m7 Y7 i3 t1 o8 ~" v- js.str.casefold()  # 去除字符串中所有大小写区别
    & q) x  Z! B8 k3 o0 l: F. U$ T0 `" q% _
    0                 lower/ a' P: \* m/ s/ s7 K4 ?  n
    1              capitals
    + i; Z0 L- u, y2    this is a sentence) r" y: }. g+ Z& E+ a
    3              swapcase! j- H" i7 m  Z$ C# S' T2 W
    4 _( p) y$ h  ^3 Q
    1
    1 P4 e% ?- n) \6 F; U' K25 L7 I2 u" U8 I8 p
    3
    7 j8 J  a% L$ V& [/ m. ?8 X40 K5 s& _% L4 ^! {' l
    5) [4 _: h( C4 t; S
    6
    * {5 i  E* }' j# G  r78 v) e# P  P; P$ w% C' E
    8; g8 J* [3 u& a8 @
    9
    # Q$ |+ T/ h" M0 m) Y2 T; I10/ ]: L$ q6 W" [: K9 t9 }$ B# ^
    11
    9 v4 \+ c- L- ]0 }) G, o- s) b5 x126 F( L$ R, s2 O$ X- }- P. M
    13
    1 V$ H8 s% @5 s( v2 g( M* v140 I) u7 a5 ?2 `& [0 ~) q
    155 V% V- f0 h1 q* v9 r
    165 H  r* a5 w* }! B% k6 [% H5 b9 g+ R
    175 ]& G4 Z, Y: n  b! e6 k
    18$ w* v6 |5 ?) V3 c5 V: R
    19% O$ j- f& l9 d2 j4 `. Z
    20- X8 o0 `8 G3 ?0 s/ I
    21
    ; w$ o& l  G1 t4 m22: a6 q* K9 g( N0 f9 E' D
    23
    4 o  H" X+ ?) f/ w  ^, @: ]' b# O0 i24/ P! x) w1 w& o5 Q& h: K: J% s
    256 d8 {& {; X9 n
    26& S% f% F  T' r# j# y0 O; a( h
    277 m% S7 _0 z7 C/ T' X  H. }4 j  r
    28
    ' U* N4 _! F4 ?' I29
    + B" Q. Y; D8 Y1 t) K30" @( r7 A% ]2 t$ j
    31
    1 D( Z, @: F6 e32
    0 b+ h$ ]4 T1 ]( O33
    + ~$ w+ C8 F; v1 E34# A4 p6 B. ?6 K
    35
    ; u5 x3 F1 W7 T- E2 D2 h6 t36
    9 X; o$ I6 e, M37
    5 J* O* G6 N* i) C3 [3 \382 W$ L3 @' y  Y% l2 K
    39
    9 b' g6 r% v1 L0 n/ S3 L' u2 A40
    / ^  _7 ^. h: t% G412 c+ G5 J2 ^9 Y( [- L) W: G8 K
    42
    ( D8 Q! X0 W" ~9 P1 l) z43- b# U. ^/ i& ^
    44* ?% U- M3 _& H: Y2 T+ t
    454 _) Q/ c% S- u1 {, k
    46$ V/ B% b: ~/ k3 f# C3 y/ S! v: a4 z, K
    47
    / S, \0 G! {* |( X: z% b48
    9 s1 a, W' P$ K5 h$ s, f8.4.2 数值型函数5 X) i7 ]/ s( V- U0 ?$ m
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:3 ^) j* v. h+ |
      F2 `& q1 D4 u7 j; S8 Z
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:+ Y7 M! g3 \+ v0 D: l! E" T
    raise:直接报错,默认选项7 n0 Z! d: y" e) M7 h- c
    coerce:设为缺失值1 b- G8 l3 i' }4 Z9 O
    ignore:保持原来的字符串。
    $ ^% b1 ~3 f- C4 K* _. ~2 xdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    8 I" G7 ]' F3 V. K$ a  D* h1 y1 i" _s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    8 {* A. s4 @3 j
    $ v- |. [* s% @% z2 }4 Ppd.to_numeric(s, errors='ignore'), P! ?0 z# E5 |7 e
    Out[93]: * N: O" A0 N0 K- U
    0       1
    2 ]# f% p# F% Y9 {* K+ n1     2.20 E6 P5 }( A6 X9 m( @  K, t; K; V
    2      2e+ L7 G# u9 a7 P3 d) d
    3      ??5 Z$ z2 E( t% Z3 C" T
    4    -2.1) a/ J5 p6 {8 m4 @
    5       0
    - m# M- u; \* X' T/ jdtype: object
    / [8 w. U* j7 I$ P! L+ m0 N5 b
    $ J3 v& B  Z2 o5 S7 V" c8 r3 m7 e9 Jpd.to_numeric(s, errors='coerce')
    0 M1 k$ p  n& t2 m' S5 mOut[94]: ) b+ M' Y" n4 B) _1 ?0 F7 p/ V" ^
    0    1.00 A; l: S# t" T/ a7 S" B2 o! [
    1    2.2  i5 i! u9 n: B$ J, o$ j
    2    NaN
    / _0 t! e. I" G6 O3 Q3    NaN
    7 I) G- }; c5 U; ^- ?5 T4   -2.1
    5 b5 i5 e+ Y$ M  z- h: N/ H& k, x8 R5    0.0
    + n) D3 p( b6 e3 d/ C0 gdtype: float64
    3 p; N# s* t$ A: ?+ b' E, u# P4 H/ c% m. d# F, f  h1 w! ]
    1
    * ^# `/ X6 _/ M$ i) R/ A8 Y; [2
    * y' W. S4 B" U0 y" ~3
    + g' u! _) |3 n/ Y4
      b. ]* f5 Q. y5
    ; c5 D& m% ^9 E: w62 A. C( h# I2 p* r8 Z( l
    7
      E* x( i7 r( o8
    6 [7 E8 k/ r) u, l9
    + u$ D4 W8 M: c4 q8 {5 X' l10
    1 u$ U; |0 n) A* Y. @11( Z; }9 A5 W$ l  s0 T
    12
    * \4 Y& I4 L9 i! m  v& c) ^138 J4 D% X# B- F+ v/ c) G- d# c
    14
    ! c. J3 l7 z. _3 b5 N. f! `7 R+ C6 O15
    & J, s5 l6 K5 L$ ]16
    3 T. u  |+ `: p0 `( j+ A! }9 M; g  [17
    6 m8 G  F5 a: t8 P" L18: E4 r+ |5 _) R$ h' M
    19
    9 y) e1 w& ?! q; r+ }) @& E4 F202 i8 k% W; h9 t5 ~% M+ F: w
    21
    0 i) g) g* v* H6 b  C: q  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
      d- T! C! W8 w% D/ c2 K# T! Z3 c0 u* d9 Q7 a6 r/ @
    s[pd.to_numeric(s, errors='coerce').isna()]- _5 y% m* \/ M& q6 p; a5 x
    Out[95]: # T3 N( M8 l0 g4 ]
    2    2e  c, ^. s* w" @0 }- Y2 W
    3    ??) f$ Z  o8 u' D0 ?
    dtype: object3 A% {8 @+ C2 N8 S+ }
    1
    6 c9 R; O4 _9 C) Z  U( j' `1 p7 |* ?2* A& ~/ n" Y' Q1 T, p
    36 Y; N* f( ?3 I
    4
    " J3 V" i, X8 D/ Q9 @5* Q7 F' ]; V; Y- b. B: c. N
    8.4.3 统计型函数
    $ c& S  y6 f; o1 Z4 l  count和len的作用分别是返回出现正则模式的次数和字符串的长度:
      ~& a: b( ?0 S4 N
    ( {7 ], G7 h; ^3 \- `4 S' n% W! Ss = pd.Series(['cat rat fat at', 'get feed sheet heat'])1 I, `; @2 D6 f4 C
    ( _! E4 g! l/ O( U! L
    s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次- t% c# y6 W! `0 g' ]' \$ x
    Out[97]: 1 R5 h) {0 g5 K! e
    0    26 x  d: N' x2 G% }( P
    1    2" o8 Y% }( e0 p) w& N, }1 q
    dtype: int64
    & s+ L% H& q8 d5 g; L/ B2 W) v: f
    s.str.len()
    , V. {  H" S3 M) Y: d! KOut[98]:
      l  E0 D# c( V; K( Z8 {0    14) A; S1 y/ l5 A% [
    1    191 c) Q* l, _4 {9 P3 [$ D
    dtype: int64
    8 z0 u4 _  G1 T# \" ]1" F+ w( |7 Q3 `
    2$ \& ~% E) V5 K; [
    3
    # P; u* D# s. A; H4
    0 _& Z) A/ I" \+ p0 q* x1 b; g5# x1 I5 y9 G' U6 i6 {6 X2 g
    6+ m8 R1 ]) [  S+ Z2 E4 `
    7
    . w! s) R& G0 ]- U1 y! n8 o9 J87 y. m  I' A$ q7 O/ W2 z
    91 Y) o) y2 U9 E
    10* m# z4 p8 a- \
    11* G! Y, G' a$ b0 I: Z
    12
    0 v" Z6 R; j2 j. X1 Q% M/ i' @132 U' }$ F0 @2 e
    8.4.4 格式型函数
    3 |! W9 A3 B' H+ h5 ^$ t/ K6 H5 |) f5 L  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。# H$ f2 Z( D" M( X

    - s3 |" y, N) J7 [' K: F: }+ Xmy_index = pd.Index([' col1', 'col2 ', ' col3 '])8 C- u, z& m9 |, `, R8 C9 T2 {8 ~
    & u& \) C  v* t* R9 g# n
    my_index.str.strip().str.len()
    ; i3 E$ m7 P6 x& bOut[100]: Int64Index([4, 4, 4], dtype='int64')
    5 I& i# x: F: y: S6 ?+ g/ N9 l* x2 X0 S( j6 K
    my_index.str.rstrip().str.len()
    1 w$ a+ {" G! IOut[101]: Int64Index([5, 4, 5], dtype='int64')
    1 ?3 b7 u" [! F4 R; \
    ( @0 m5 M7 j+ M  Xmy_index.str.lstrip().str.len()3 x" w, Q7 T/ g* |9 W
    Out[102]: Int64Index([4, 5, 5], dtype='int64')+ E, V0 ~$ ?% C$ _3 c0 Y7 R
    1; O4 A- Y' z: M, ?, y! D, V( O+ J
    2
    8 x  n& N& h9 N' g. P- m6 b3
    6 F8 D7 r2 x9 {- d) F4
    ) `# |/ H( l& h8 A" ]% l4 ~51 D4 M4 k: z  S  d. L
    6
    ! H1 N) ~4 p1 K. w2 i7
    : u9 g: X' j! E9 G6 B# c8; g9 z3 g& w! w
    9" l* O8 n; P3 ?  }+ r4 S. W* x0 e
    101 f) S. O; H2 o
      对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    " X8 o( e$ r, Q4 j; l4 ]1 X- U: v+ m1 e5 J3 x) l: [+ o
    s = pd.Series(['a','b','c'])
    & z4 N: ?0 l; r, x( q+ x: W& o8 ^* `, x5 d- M7 U8 x0 P+ O
    s.str.pad(5,'left','*')
    ) j7 r, R& V6 G& e9 iOut[104]: 7 K; u! h  k. s& x) Z
    0    ****a
    . C, i7 ^$ q' B: e# S1    ****b" o" H8 `. V- D" a* I
    2    ****c  P) X! k" R9 h! u. E) x# \( u: B
    dtype: object
    0 ?1 N; \  J; R
    : m! O2 [  B6 G5 ^7 m" Rs.str.pad(5,'right','*')7 D+ K$ `2 {8 b7 }" Q% j
    Out[105]: ' w( k0 s+ l6 d/ L4 g0 _
    0    a****
    1 h) e# r$ N3 I5 a# P2 T. n3 H1    b****
    5 @$ T, v. ?8 e- j* M2    c****
    . p( t) N- M, k) T/ bdtype: object
    : n1 @. \* J1 {% ?* c
    4 K* h2 n+ C' X3 C; K! ds.str.pad(5,'both','*')
    , C1 u% _" c4 `; uOut[106]:
    5 }  A  \6 k8 d+ q& s1 ]' }6 W0    **a**
    5 X- \# P: U0 z1    **b**; x1 o* u0 D( y; e' a, ]+ S* `
    2    **c**
    4 n: C: q. ]! [dtype: object
    % i/ \- T+ g! F; y. v6 b* Q0 w
    : h. j4 M9 r% t1
    . q8 ?3 t) {, ~# K% S& ?29 M9 c1 a" p# }! Q
    3
    8 t' U9 z5 t* l4 _/ ~1 _4: Y- j. M) a$ b7 k" N( C3 b, J
    5
    : [2 u" s) l$ q9 x9 R63 ?' {& J3 k" y
    7: t9 |/ g: D% N1 r- l
    8
    6 J8 \: |  @3 o  @- J9) i% x: V- I) Y- N9 T1 b2 A* C: G
    107 x4 l8 x( Z  W! a* R
    11
    ' K. H3 P' U$ a7 O* z7 F+ {& N: @12* P1 s0 S8 Q& N5 V/ j3 ^
    13
    * f  E7 w% _+ x* t" V14
    + R$ Z9 e3 ^5 s% ~15
    0 @8 ^7 y; n9 G* {& q165 t3 c$ s& ^# f9 ?* A% i' V6 H
    17. B% [( a6 k  d& d( P0 U" B' g' ]
    18" Q. t& G# W9 ?5 G# a/ u
    19
    7 t6 P; b; m6 q" P. H7 Z20
      ?7 o2 O4 C6 r8 E7 v21
      U; G% V0 T3 _4 b7 ^- f22' d- E/ A3 H' p
      上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:( |0 P' h/ P$ k( D$ J
    * V) m0 t" h# t  R
    s.str.rjust(5, '*')
    " m0 E' u, G( ?2 Q* Y7 x, rOut[107]: 1 L5 V7 g- W7 e& e0 A1 k
    0    ****a
    : t# K# c0 `7 k1    ****b
    5 J7 b8 S( U1 S3 l- T2    ****c
    6 x7 I6 B2 [% `5 Q+ ^4 pdtype: object+ b) W. t% K* K) d# X
    + S5 X1 P! b1 Q5 u( |
    s.str.ljust(5, '*')0 c, e9 O2 k  X" G) ^
    Out[108]:
    . E* ?1 m8 X8 {6 f/ P0 y# y0    a****; f( s5 L' v' M7 S. X. k8 O7 w
    1    b****( C. U5 k. ~$ d3 V
    2    c****
    0 g! a  E& B) D4 D5 cdtype: object9 E) K6 r+ M# \- K2 }! _  o

    8 d0 A8 Y; ]/ X1 ~s.str.center(5, '*')
    . O* g5 q6 d* P3 V+ g; W8 f( jOut[109]:
    - n  P) W8 d* e( P! Y% A: q. T" I0    **a**6 e9 G# ~" o6 J3 f1 F8 k
    1    **b**! C! K/ d1 i" }/ A3 `
    2    **c**
    ! H; `4 \- {. V  ~dtype: object; Y1 j; G4 P3 I- J
    $ Y! B2 J7 ~1 n  D/ ]
    1& a9 Z& _, y- H
    23 E4 K$ f/ |) d1 T5 p- P5 }- L
    3
    , ^4 W1 M8 c, }4, _. P3 ?; \' u! x" l# `3 `) O1 A
    5
    % q& H+ {$ L' T) Q6( A( {+ h" n% x; j
    7: e4 d- G! `0 O! v1 O" d2 ?: L) s5 E
    8
    5 k. g7 s( B/ E" U+ l" S8 T9
    ( U) s2 m' j' H5 y  e10& K. [* y$ w! j, a9 i) I, i
    113 ~" c  M6 H/ h6 h2 g
    127 A" n2 V5 A# M
    13; _4 F: Y6 ~1 K
    14
    , j( S/ g( O+ O0 Q' P0 ~0 V15
    " H* y+ d) @, F" I; c2 W; t! {16( F% O# t7 B% x
    174 t* e/ X& o: ]" O5 f
    18
    . s" P- R9 T7 c: s19
    ) d! S9 K7 F' o- a2 G205 c7 X$ i) a$ O) J( p
      在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    7 E: O$ L3 F1 |/ Y8 }) X/ m- S3 ?) @" r( b
    s = pd.Series([7, 155, 303000]).astype('string')
    1 {9 p: r- i$ F' T+ Y( B7 s* N( R* E( x4 ~( _" B* M
    s.str.pad(6,'left','0')3 g- ~. s8 f2 Y" V
    Out[111]:
    " m3 Y  S% l9 _+ h7 O6 v0    000007( v' s5 f: b; s+ L4 I! A
    1    0001550 X  P" K3 c) W- f1 C7 b  W
    2    303000
    - k# t8 o) B' D# Udtype: string7 Z8 }, P( C, ]4 c$ }
    2 G# G; H0 D$ Z* H% o* b
    s.str.rjust(6,'0')4 l3 U1 y2 W! v
    Out[112]:
    8 p# N' {8 ], Z5 T; d8 h0    000007
    $ t/ K3 r. i2 K/ o7 N1    0001558 L/ e% i: g$ X$ \7 f: F( @: y- t$ E
    2    303000
    8 N7 n2 p  M  _! D$ ]; Jdtype: string
    ' M" s' F& G# f& a( z. R# E% G' A5 f8 {- Q) E7 t* I6 B
    s.str.zfill(6)( K5 k% a( p' s) g
    Out[113]:
    , N5 ^7 `" U" i8 |2 r4 p0    000007  s* s% L0 [8 h& |: x* S
    1    000155
    3 M( T: l9 H+ [7 d9 z2    303000
    . D: ~! Q" F. bdtype: string4 X5 D, P* Q9 U4 p6 s' b

    6 [# c6 T6 F$ `6 @1- J! H7 g+ p1 a/ X) I
    21 E) s: R/ |& S* u  h! H
    39 V' Y) |, V" v
    46 g" C, C, ]7 F
    5/ A- F- b' D7 `# A6 O3 w
    67 M2 E3 y! n* ~* t, T6 B6 k  g) h
    7& g$ Y, d- m1 r$ ~! d
    8
    ' c# ]5 m( k5 z+ N7 A97 c3 o# a3 o+ g6 R: q3 K1 m4 z
    10" l. Q( v  ]6 E9 f: S' M
    11% g: V: {' {- V$ D0 W" o8 @
    124 l" g  j! E4 T& E% c' ]( G4 E
    13- `9 a7 B$ x6 s
    143 O3 |! p& n+ J  |
    15
    & C# I" c( c# m; c: J16
    $ ?% K- ]' E! p! L" A0 j17
    9 K0 d- ]. f( R% M% I/ V18: o& M! s' |. ~! m
    19
    / N: s- F; T+ P$ [4 z) T; R9 M- a20
    # Y8 N* h4 m2 Y1 n9 ]( P21" I6 M, a+ F$ Z3 m" ^, u6 `' r
    22: }% @9 t  ~1 r, P! {# p! B
    8.5 练习  Z$ \8 m- i2 s( h% K, J- C  O
    Ex1:房屋信息数据集
    . m6 W  V! k: @" z7 M, ?! i现有一份房屋信息数据集如下:
    ! z: m" E/ i$ u/ _& K" I0 M
    1 ]' d( Q& n- `1 J% B/ cdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
    9 }; Y, e; d/ Kdf.head(3)
    + [( _/ }( v8 H) w0 q- BOut[115]: , k6 f: M' c. e/ Y4 T+ L0 k5 U
          floor    year    area price) |- l, F9 t2 i8 ~. C* \, U
    0   高层(共6层)  1986年建  58.23㎡  155万
    6 U6 \! Y9 N) n* H/ \1  中层(共20层)  2020年建     88㎡  155万
    1 y- x  T! R. X0 z2  低层(共28层)  2010年建  89.33㎡  365万3 H& D4 F7 z- D% L1 x- ~9 j- t8 A
    1
    ; @6 f+ h3 L# D% |( D' q- Z2, s+ d6 _5 G0 H; L9 m* G. X
    3' f  S1 ^$ U- l5 ~, i: O1 G5 p" j
    48 N* ]* A5 }+ w" l+ ?- O
    5
    " {3 T' o3 }# L9 Q60 }1 H$ F+ V; o) ^: ?
    7
    % Y' p/ ?- T  x: t7 P3 j将year列改为整数年份存储。
    7 e" K$ v1 P8 Y. j6 `+ I将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。" H" M! z6 T7 a+ `
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    7 @; B# \/ u' K# Y9 H将year列改为整数年份存储。
    $ A# q- G! c: u: D5 r" a  a0 W3 D1 ]"""
    6 t" p  W* e. \* O! b% x- a整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    9 a; O; U- v# u& W. x注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    6 W; {5 g! U3 n9 j- m转成int后,序列还有缺失值所以,还是变成了object。: ~% q& Y6 D7 U3 X6 ]: F3 x
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    0 D1 K& b( `5 e  T! U"""8 s  B2 I" D# _/ t' V" D
    df = df.convert_dtypes()/ h* C4 `3 Z. ^) P' {6 L' V
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')! t* ^/ ]* B3 I  f: ]
    df.loc[df.year.notna()]['year'].head()
    5 ?) z) V; k% ?$ J7 ?
    " _1 m7 K5 A- N, b8 v! v0        1986& S) @/ `7 P0 G: f) V
    1        2020
    ; [8 {* `$ L6 Z: j/ t2        2010) L: S/ s5 ?" K/ P; T# ^6 b
    3        2014
    ; I0 M& v: B+ ?8 T4        2015+ c) f1 [" _3 Z( |
    Name: year, Length: 12850, dtype: Int64
    5 B) z0 G7 a2 y' o7 p) m; E+ D" M  B! I1 o3 S1 k- }3 @/ f2 c
    1
    2 E+ L+ n) Q+ l+ k7 J2
    # \# W2 I2 I4 ~* y% F  X" ^3
    5 N$ p9 B$ D$ |3 ]2 h% Y4  Y; P: w& A& I: n  c, \
    5; Z- w' A+ _; l) _7 A  y( N
    6" ^$ j$ w* W8 Y6 Z; j9 m
    7( l# q% |$ o( U( m
    8) {( V+ t/ M7 Q( z# n/ e6 K+ I
    97 g3 B$ x2 N& L, D- j
    100 j3 _8 P' Q! ]( s5 n, L' K
    116 p# ?/ _+ L0 n# v4 g  j7 M4 ?
    12
    $ P2 W4 w* ]& ~( G8 ^  A5 k  \13' g+ M  w4 R- V& N7 p0 F
    14
    " k; o- A# Z! F6 R5 ?155 h6 q8 H+ t! b
    164 w8 e; r  y* h: J7 T' s
    参考答案:
    0 @. H2 Z6 S% s+ k' r, p  l5 Z9 _6 W* |# _7 N! O
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么  F* v) v/ b" p% Z

    ) `$ d) S# v' s- W# G" A. ndf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    ) }  Q$ `1 x2 u4 c7 H" sdf.loc[df.year.notna()]['year']
    ; ]( b2 B0 L; U& y, v# e7 J  E1
    9 N* P5 I  y7 u- x5 i2
    ( E! x4 t7 g. W# G+ X& B0 h7 n9 {1 f7 u2 X将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。/ i5 t9 E! C# u+ U
    pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    : h$ Q6 h- W7 f& o( y7 Q* \df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次% S1 C6 d1 D( [1 e: s
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型( v" j) e8 ^0 J& K
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              7 Z# h" \0 u5 L0 }$ D6 a8 X& O. S
    df=df[['Level','Highest','year','area','price']]
    $ s0 r( ?- i% N4 U' kdf.head()$ j+ i/ ~( M* _& \& x

    . l( B. a! }$ O. h   Level  Highest        year        area        price, ?. j" ~+ l; G% }) S1 K
    0        高层                6                1986        58.23㎡        155万/ b/ ~5 {6 p( J  R/ |
    1        中层                20                2020        88㎡        155万
    % a  Z7 n" u1 s% r- i. V2        低层                28                2010        89.33㎡        365万
    1 L! s0 C: `% T3        低层                20                2014        82㎡        308万/ j" x8 l0 t- g! \; O% H9 i
    4        高层                1                2015        98㎡        117万
    " l$ A1 d; x: X* o' E5 l4 p8 b1$ u; o2 T- N4 O9 h0 X( S3 m$ U
    21 B* z' I- P1 x- d6 N1 b8 r
    38 N+ m0 r; j& [5 a1 W; a1 q
    4
    ; A6 M; t5 }% \4 h5
    5 T3 ^2 O: y( G( C1 ~9 ~" n' A' _+ Q69 n, n# w2 `3 v7 Z" w& f
    7( ~+ J1 e9 d# \! \
    8* u2 I- }8 N9 r, q" O1 C, X
    9
    9 O% Z$ H& D/ `3 _: ]2 Q10, i0 p2 M( x  y0 Z' j9 N8 H5 h+ I
    11: m* S* r5 f0 y
    12( Y: j/ I2 I7 X+ W; Q
    13
    ' J5 T. p; \. r' k, |  F# v! j$ s3 s# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
    ) m5 |7 d0 O& npat = '(\w层)(共(\d+)层)'' [3 D. y1 L6 c1 h# a$ i1 ?2 i$ q
    new_cols = df.floor.str.extract(pat).rename(
    8 J% }  {8 {5 o- _3 t  d                    columns={0:'Level', 1:'Highest'})
    ) a; V4 `$ }( j7 [( r/ n
    / S8 E. G- d. A1 Y0 Cdf = pd.concat([df.drop(columns=['floor']), new_cols], 1)) N. N8 Y0 ]) E; [
    df.head(3)
    2 V- a, `" x' X9 t7 E6 V3 m3 g. L5 l+ C. M1 {  O, k" D) {
    Out[163]:   ]+ K* x" E1 ~  z% D# Z1 P+ W
       year    area price    Level Highest
    & t/ Y" k" t. {& o4 F" V& Q0  1986  58.23㎡  155万    高层       6
    0 M* R" p+ L- {* R+ H+ R$ s1  2020     88㎡  155万    中层      20  J. ^! _7 \; `# l" K6 S. r
    2  2010  89.33㎡  365万    低层      28
    ( E4 M& t' Y! U0 \! z) X: T1
    # a* n' t3 c! s: H9 Y( v# M( U20 i7 n7 C3 j% @5 |
    3( s! p, Z& W+ [# f" f
    4
    1 t$ g* D4 W. m. L# V! }2 t, |5
    ! D2 }) _; A+ Z+ O0 T$ r! ^66 ~( a: R7 P- W( _* h, E; X' R* k
    7
      J7 w. D. x. B* Z( `8
    5 g+ I, `+ C# l5 L! Z7 E99 {) y; ^# @" u* X* T- {. v
    10- {% f3 P' H; b5 C6 e3 Z
    11, r( U+ C+ `4 o5 [! O: e7 z
    12
    1 U, A/ H! G* q( W8 ~  V) N135 `9 `2 ?8 e7 O3 `
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    ( |+ Y2 o- g# o"""
    % @& I+ U6 ?+ u  T+ y) lstr.findall返回的结果都是列表,只能用apply取值去掉列表形式3 L, e9 B3 Y4 q' X
    参考答案用pd.to_numeric(df.area.str[:-1])更简洁% c2 G$ O9 u* n/ n# I% O
    由于area和price都没有缺失值,所以可以直接转类型) ^, b( U; l! w
    """4 r7 e4 ]# H5 E7 I
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
    ! N- q% u$ `- ^8 S- L$ q) I6 @df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')  i0 g8 E5 C4 v" M( g" A2 W
    df.eval('avg_price=10000*new_price/new_area',inplace=True)
    & L* L' u; _' ?4 L4 P' i6 J3 [2 R. w# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))$ {0 x7 j* S" Y3 Z
    # 最后数字+元/平米写法更简单* |: x, G& w# ~' T* U+ u, g
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'6 c* k% k6 }2 l, n/ t& }* V
    del df['new_area'],df['new_price']8 l! c- n" a) z# c
    df.head(). G! `* l( j6 ^& w- s  _2 w

    " c, u) g* h' ~6 w/ K   Level        Highest        year        area        price        avg_price
    1 G% B* Z$ K! A1 L0        高层                        6        1986        58.23㎡        155万        26618元/平米# v; T" Q! N- H4 x( y- x+ k
    1        中层                        20        2020        88㎡        155万        17613元/平米
    ) p$ T$ t. G* o; K2        低层                        28        2010        89.33㎡        365万        40859元/平米  T# J1 n& [/ b/ D6 H( `
    3        低层                        20        2014        82㎡        308万        37560元/平米5 j$ P4 V9 R! l& l/ L9 Z5 L6 h
    4        高层                        1        2015        98㎡        117万        11938元/平米- J" N, E, C; p$ ]0 c5 @

    ' G; d8 C$ o7 x; r( _& H1
    ! H6 H1 d: d9 V( N# t" T0 R2
    + E- g+ x. G7 t6 x8 g  u32 f8 l4 v3 R- m# }* y% o3 Q# z
    4
    " B6 y; d# N5 |- ^5
    + r; z) j# X6 f  n/ _8 L1 r: u6" L7 U, ^5 }& y) K
    73 ^$ Q/ n+ t! E) L
    8
    4 d' o. T8 Z) b/ R# V0 Q/ Z1 b9
    8 P( g- d7 |$ A6 l4 N' f8 l; @5 b103 G1 T/ p9 A9 E" r2 n# f
    11
    + ^  h' \- P& z7 M' E, |121 ?8 q) @1 a" B" P
    13" p. H* G* D) Y( x* Y' e6 W
    14: {& I+ l# {6 }! ~; k+ _
    152 [4 X) t9 l0 Y% L8 L/ M
    16
    . E  D! C* _/ e5 Y" _17
    , f6 q3 E7 C# q- P0 A8 a: ]. K* i18" f% i: R) d: r, j
    19
    + \/ F4 T# q# a4 }/ X! b( R20) ^7 s& |8 ~$ x2 l1 k4 Q8 C( g
    # 参考答案
    $ h" K8 M# x) O6 ns_area = pd.to_numeric(df.area.str[:-1])4 h- x) E; @7 I) t# b5 d/ |& j
    s_price = pd.to_numeric(df.price.str[:-1])
    , Z# Y. s3 V0 h( L; r  Odf['avg_price'] = ((s_price/s_area)*10000).astype(9 H, I0 N) x- J* {
                        'int').astype('string') + '元/平米'$ K! g7 t' G1 [& ^5 [9 t& }6 o

    6 h5 ^% W/ R, i5 X  e+ ydf.head(3)& \# O0 W6 J6 s6 }" x, C9 s; o
    Out[167]:
    1 g. a# }* l0 T   year    area   price   Level Highest  avg_price+ r. W7 x0 L$ O  V
    0  1986  58.23㎡  155万    高层     6          26618元/平米( y4 r- R0 a7 F
    1  2020     88㎡  155万    中层     20          17613元/平米* Z! O0 W  B. Q; ^8 ~1 C& ?
    2  2010  89.33㎡  365万    低层     28          40859元/平米
    : g6 Q* {5 d$ {5 }1
    4 U: l5 X7 k! q) w: k  _25 m. a5 j! v; t; p
    3
    % o7 @9 c6 b& |$ o8 u+ _4 V9 L4$ \% n* W: _& J2 M) y
    5( H" g2 L- [. \6 H# Z1 ?
    6
    * |2 u7 s+ F# X$ e1 D" n- W7/ {, K% W; ]* t
    8
    9 G! g" D+ ~" `9 X# c9' z  x! ]% C0 B
    10+ B# E$ h3 j( z- t4 O' ^& U
    11( l1 H* J( A: W8 @) r  Q( h8 j
    12- A; e/ r6 ~( z5 V( c! g
    Ex2:《权力的游戏》剧本数据集8 W6 `+ v4 s* h( ~0 Q; f2 K3 V* [9 ~
    现有一份权力的游戏剧本数据集如下:
    / {7 [8 X3 j' n# o+ y- i* ]9 s3 e: i% A7 ]
    df = pd.read_csv('../data/script.csv')
    2 E9 O( s0 I0 O: s) e% b( bdf.head(3)
    % l# A! j' w, s( X) U
    " q2 f" G+ i1 tOut[115]:
    ' M; Q" V/ O+ Q3 i1 j' |Out[117]:
    9 F. T  K2 j# w* q5 l! k8 E/ |  Release Date    Season   Episode      Episode Title          Name                                           Sentence
    % m/ o+ v+ F2 j+ O( M0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...1 b: O- \% ]) ]8 g; X. l0 K
    1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...; d  R' ]1 U( x; u! n
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    3 S6 O- S+ v9 N2 I1+ G# b' o3 S9 [) f
    26 }, n5 I1 I8 y' Y) n
    3  o1 K4 |: c+ l1 {. E. p
    4' f% K$ }7 g/ l( c( _
    5
    0 D- b2 i9 l5 f/ t) n: }3 N6! L7 u/ d% k/ \; g0 A3 q
    7: O! V% C' `4 o5 q  s5 U
    8, `% B; v" Y, k  o. A- v4 S& R
    9' n% m3 M* P# X0 j9 _
    计算每一个Episode的台词条数。, F0 Z; D1 J0 J4 f/ |& t
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    6 F& W* K/ K8 ^, X* j若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    : ~" o3 J7 w0 h计算每一个Episode的台词条数。
    ; d8 }# F2 ~9 C% P5 r" |df.columns =df.columns.str.strip() #  列名中有空格
    # ]' s/ G* b. m$ l% Mdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
    1 ?/ G( g% u/ x
    8 u6 x& c3 h* r+ U+ ~( wseason    Episode  1 t3 |: F6 Y; }& [% v) h: d+ u
    Season 7  Episode 5    505
    ) b& g9 e% M( v! O: V1 B8 @Season 3  Episode 2    480% w+ Q; W/ e4 N5 S3 o& |5 S+ W3 ]- f
    Season 4  Episode 1    475
    % `, @4 ~# h% L8 z5 sSeason 3  Episode 5    440
    0 o0 {' O3 c6 N  ^! [Season 2  Episode 2    432" H/ J, a: \) P! R) S, H; N
    13 h8 Y% {7 f% K! R$ Z1 h7 z0 M
    2
    3 Q* p9 \+ O% s& P" {. ^3
    ) B$ d4 {5 }9 _4
    2 s  L$ V: w+ K7 T1 e5' N) `0 j$ \- F
    6) a: a0 B6 c9 U
    7
    5 p' m* E( [0 J& y$ {8
    . t* r' a6 a+ ^$ u8 N9% ^: d, C' A. H+ v+ M# C
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    % |  o( h9 l* J1 J* H& m# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数$ H. Q! ?. {0 l7 E% ~
    df['len_words']=df['Sentence'].str.count(r' ')+1# X2 `$ z! {% B
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    5 b& H0 ~5 R) q! l! N
    7 F' r2 I$ l$ G4 KName# f0 m5 ~6 G- z
    male singer          109.000000& S8 G; Z* v5 E; p8 N" z0 N
    slave owner           77.000000
    / Y( o' A5 {9 l- T' l5 nmanderly              62.000000
      a- Y2 ~' E- k& |4 J4 Qlollys stokeworth     62.000000* Y+ d7 J4 E6 {- [1 z5 r7 L: M
    dothraki matron       56.666667
    ' X4 l- P7 [1 H2 [, SName: len_words, dtype: float64* D4 T, P. g' [, W3 r6 Z
    12 F8 @4 G9 I$ r! @0 Q  I
    2+ c; j1 T9 Z* k6 Z1 W4 i
    3; h! C9 H# i& }4 Q! O9 O2 Y
    4- ^# n) J9 ]0 [+ D6 Q% Q
    5) Q/ z. x0 O7 j$ _8 R
    60 A6 m& p5 c' B* ?( H7 P# A( Z5 B
    7% b+ I- k+ M; Y2 Y
    82 U: Q5 r- L' }  x' F1 I
    93 G4 \, I6 |$ U8 l1 |
    10
    3 a* ^( M  d  P, \: Y5 x9 ?11, n& ^. F) w( K: n  p6 s
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。  z- ~  C$ e' Y: Q( l* I, V
    df['Sentence'].str.count(r'\?') #  计算每人提问数
    ' B6 r$ u5 X" q4 _2 {/ {  als=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填02 Q+ f9 ~$ \5 A1 u+ o2 s& i
    del ls[23911] # 末行删去
    5 Z, h4 P" Q; Xdf['len_questions']=ls& q- }2 J' L/ M# t: C7 I3 i" j
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()' U9 _$ c- S( Y. n' I+ I
    $ @6 I$ n! Y+ H3 `7 o
    Name
    ( o# X+ |' C3 H) D4 Ptyrion lannister    527
    ' X! j+ U' g7 u1 u! G: \jon snow            374. p- Z' B: g6 p! W  y  a' h8 L( \
    jaime lannister     2836 @9 K$ }% p) y- r
    arya stark          265
    8 `/ P6 t( h+ A6 P# E% rcersei lannister    246
    1 p; z/ }( \, S1 r/ `8 qName: len_questions, dtype: int64
    : k7 {( f1 C8 Q" @; m$ \
    ! j- W: [3 C: t" D, }' Q- R' C# 参考答案& _% J2 e7 ~% ?' m# C8 C$ E7 Q3 _
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1)): N9 k7 h- R0 P5 d$ k
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()) S! p  [8 W9 P' o4 ]* W) S$ P* O' {
    9 D4 T) q& [& \
    13 i( h2 q* E$ I8 L6 R6 O2 c7 M
    2
    4 t2 H1 `: k& u6 e/ `/ T+ g2 u3
    ) B* w$ M2 F. A5 b* C/ Y4, D# c: d4 O! t% k2 N6 o
    5
    & N# S; }; g* {6
    6 a9 Q8 L2 S4 s, c6 o$ Q9 X7% t5 Z% ?; J6 C* C
    8
    + V+ y" |. w0 N3 @1 V9) N* E& j' f% e0 Y2 R
    10
    * S9 q# @- T. d. @118 A: [3 F$ N7 O! G) u; x1 _
    120 ~3 o& l4 ]9 |/ b( ^/ x
    13
    / [2 M4 `0 b  T9 K; H* R147 v# S) ]' K8 w0 v8 Z
    15
    ! Z  A  g7 v* ^1 k7 y: W16! u: o: T5 n* k! X1 @# C- q
    17
    1 r" O, ]* x  f, V% r' Z7 x  `第九章 分类数据) }6 _$ |7 j4 X# C( ^
    import numpy as np
    6 P: b+ {9 ~" v8 c) F! mimport pandas as pd
    ; R4 z0 y. A7 B' B  M8 h6 C7 H1
    6 Q1 T7 T: J1 {: {9 J: O  E2- X+ x" D+ N8 O
    9.1 cat对象
    & e0 r  \1 |' j7 Q. }9.1.1 cat对象的属性
    ) K  r: y; Q  [  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
    . k0 w& X2 p2 I/ L" U/ {. C$ X$ O% s+ z" t* i6 r
    df = pd.read_csv('data/learn_pandas.csv',+ I8 ~; l1 l, U) v' p! N
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    4 b3 C" K: C3 ]' ss = df.Grade.astype('category')
    5 c" N# x" C* K/ C) s) ?
    # |4 r7 i7 t* n9 |s.head()
    6 E9 W5 {, ]4 r1 S2 B2 G' XOut[5]: , N5 Q3 w# I! Z; Q, V  U
    0     Freshman+ Z8 z! x7 H1 W: c
    1     Freshman
    - p/ X! f) m9 {. ^1 g" h2       Senior/ `- ^: b' z: a* L/ F" ]# |
    3    Sophomore0 x6 S; o* M; W0 L
    4    Sophomore
    2 a+ [; s0 }8 l6 LName: Grade, dtype: category. v4 C. c# q- J
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore'], e# H6 z! d3 W7 `, C3 u
    1
    7 D/ `7 A  u1 }. N0 Y" d2
    2 a/ W3 D8 W& ]" _. ?3
    . {9 `( J* \4 L0 s4 s+ C4
    - P1 m1 F& `& l3 D59 B9 p5 p8 m6 w' ^0 D
    6
    . U9 X. v. e6 l. B7
    2 i5 I0 I' T2 C/ \9 f. W3 I  U8) o* t7 G3 }. [8 h: [, y
    9& E' V; Z$ l) f  r1 T
    10
    % ?3 S$ C, _" {1 s113 Q- x7 B8 K/ r
    12
    & f3 F3 x, B# [# ^: Z13- u) s5 j+ }9 h! t% I
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。2 B: e  {8 e' D- X2 p( c2 F

    3 B, ?0 Q6 w/ R1 ws.cat
    ! d* ]6 T- C) B! cOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    $ X; f- c4 r/ _) C7 F* p1: c/ C* W% `0 h( O& L- Y, B. T$ H
    2  Q9 f2 c! {# }# E) A. b$ t, {+ u
    cat的属性:0 a5 x+ b  n$ ^. x6 C' J8 Y" e4 w

    % s' _1 h, d( p2 lcat.categories:查看类别的本身,它以Index类型存储
    7 h& @" e6 l4 V3 o* Kcat.ordered:类别是否有序
    3 u' q% q& U1 R  Kcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序8 o+ e* N/ \& G3 j+ ?) R1 |7 f+ M
    s.cat.categories
    6 u: I# P3 b0 a& ~- {4 ]" lOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')8 `; t! [; }- i& }: u

    ' V7 x- b6 i. ]' a/ As.cat.ordered4 C2 a4 ]) C& _3 U9 T
    Out[8]: False
      W' W) p6 R, E' X# V1 }9 h0 Y) Q  ]: z, n! Z5 b4 N$ k+ i) t
    s.cat.codes.head()
    / `' l$ j7 ?2 COut[9]:
    ! I+ s: k% ]  u6 f* K. v0    0
    4 j' ?& h/ n6 _/ Z/ f9 s1    0
    - D& @- b) D- s+ a$ U# B2    2
    + {% J1 r7 j  f6 [; Y0 J3    3
    ! F/ _2 f0 l4 y4    3( g6 p5 ~) _* v( j
    dtype: int86 c0 U3 F% c# q- U0 B( H5 l( Q; Q
    14 E1 C* j5 v; m0 u+ R
    2
    & D# S. @' s- V6 }! n3
    & E2 M; ~5 w7 U2 F: Q! F$ W4' X# e& x' W! M* ^
    58 M& f  W6 O" f2 Y* Q/ a0 K
    6! A+ O- J! ]) G" ~: C
    7
    . J( C0 n2 y6 A7 v8
    % U. Y$ T1 ^# l" T# P( p) r) f9
    # m4 [. s" e" ]6 N+ P10
    1 \/ I' r  r- w11, l/ q" F: P; C) u
    12
    % q% a" j) N2 C13. x$ c$ j+ x, d4 y: \/ I2 \
    143 x8 n6 e1 X$ i9 _% [
    9.1.2 类别的增加、删除和修改8 k# T8 v, O) k
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
    7 @- R( z% \1 ^  K) D
    " Q+ _2 v! S6 z- h/ y' T【NOTE】类别不得直接修改
      |& K0 X  \- Z4 L在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。7 C0 m; U  B+ F6 B& ~) J

    ) X. W% T( m3 ~+ G; p1 N4 t7 ^add_categories:增加类别
    " H- b; U9 h# R. c( Z3 Js = s.cat.add_categories('Graduate') # 增加一个毕业生类别
    5 e8 ?4 c0 x! _# S5 js.cat.categories6 t  ]9 Y: ?* l0 N" v5 Y6 u2 v

    7 I9 {8 q% d7 w0 I5 A# c$ {Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')+ q3 i: c* @, X) v3 [1 U
    1
    2 G( r) V2 `' v& y2
    / N! {: |6 Y" |6 D3
    4 r0 J3 y3 g0 m/ m* w2 L4 D4
    " R) y& Z: c& G7 \( s* r/ xremove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    8 Y0 Z8 t7 c" ~s = s.cat.remove_categories('Freshman')1 b0 h, M0 S0 v/ d! X* }

    / `+ }; h: c  S* x8 T5 `2 ]- }: Ps.cat.categories9 ]) ^, ]: _% f0 K$ C
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    ' Z- J1 I6 q* l; j6 M6 x& |; y& a" C2 w. Q
    s.head()
      {( x2 R1 p5 n4 F3 [9 S5 C) |! ?# d: iOut[14]: 2 q" |. F0 x! }
    0          NaN/ b, v6 \* I6 f! `$ j& ^
    1          NaN6 C$ j4 @4 j/ i4 E
    2       Senior& r1 v) g0 Z7 w: H( z/ `
    3    Sophomore
    ! a, M8 D0 n+ e! j  m( ?4    Sophomore: O' c2 M/ N1 F8 X
    Name: Grade, dtype: category$ B9 m  S; z# H. C
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    . C4 Y" T7 j  \7 c9 H1 ~8 \" ]$ l. a, Q1- o; x) d1 R2 F$ B6 ?# }! b# d
    2
    * k5 X) |) \6 O$ ]/ k3
    % f+ [! ?/ H4 h% x$ S- D: Z9 p0 Q4
    0 ]+ c& w( O  Y, e) b, s5/ T4 o  t+ J. ?- K- F0 q
    6" Z3 W, Z$ x6 A) K. ?) y
    7
    ) v3 R' a- ^6 M8# ]. m5 g0 t) p: D% Z
    9
    6 Z7 Y7 A& ?* p+ c8 G  b" N+ I10
    % A) k; r$ A; n+ B9 t% w11+ |" {8 F* Z. G2 M$ V) K
    12
    ) ]$ `9 B! y  i( A13
    - o9 j* l5 N; k% D3 y: {9 s14( N5 F7 i) _3 @0 I7 x4 q: m# q( o; a
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
    - r- J+ S; X5 j$ {8 M% K0 O* C: I; O1 rs = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士9 h- ?" Z1 P" X5 V* S! o( X2 L
    s.cat.categories" |$ u+ T$ H$ _5 m2 {) A
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
    1 C1 A$ R9 H" }0 j( \) t( q/ F* u% H
    . P$ }: G' l7 M8 l0 \3 r: {s.head()
    2 F9 N8 p. V# j0 OOut[17]: 9 v4 o& S9 K+ ?- f
    0          NaN2 d5 N$ S$ P8 ~8 o( w0 U
    1          NaN; y. \, M! Q3 G0 K$ z: M$ a
    2          NaN2 d3 f! ?! c3 `8 R. [
    3    Sophomore
    4 ^* _/ h6 A) g+ [$ I4 \% K4    Sophomore8 k3 ?9 S( W/ d2 e6 Y
    Name: Grade, dtype: category- [+ s3 y$ V* M8 h$ N' T  e- t
    Categories (2, object): ['Sophomore', 'PhD']
    , q, A; F8 d9 M! @: g$ Q/ z9 K7 O1
    7 N5 e3 P) {6 S% Z2
    : [' P/ t; H; [$ q' t3
    - r. M" i) L% s; H4
    $ |1 C/ [9 x8 D; [' i9 \0 s58 {& d) o4 \2 ]4 W( M/ l8 s- O% y
    6
    ) [) q/ q. n. ?% d- ]7  Z# W0 P. x. J, X# N/ g9 [/ D
    8$ w9 M. p" U; ?7 g4 w
    9+ e3 E4 P  p3 f+ J$ R2 W0 P
    105 q6 a* h; A& c$ w% b) x3 g
    11
    + @% B$ f% d" A/ V/ p12; Y- _0 S0 t$ m/ K7 f& Q/ x5 U0 |8 a
    13
    " V  E0 Y1 Z* o% P5 vremove_unused_categories:删除未出现在序列中的类别
    8 H+ n, I, J9 Is = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    - E8 G4 T: u6 J! T2 Fs.cat.categories
    + w2 f) S* M1 j6 I$ a- X! {# X$ p+ V) N* R9 Y2 @: R$ p
    Index(['Sophomore'], dtype='object'), c( l* {  ]8 d; w$ P
    1
    $ N! s3 }/ V/ z: f; @1 a2
    . ?5 l4 [( c8 t; ^2 X6 @. U+ ~3
      l! f9 ?! ?% s49 a( D: p3 N6 ]2 \0 y/ K! Y
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:5 Q3 ^2 m7 d* \! o5 g' [0 w0 ^
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})1 \. C4 \6 Z' \; q
    s.head(): F  m" D, Y4 A  v

    2 e$ Q: O4 T" A4 e' y0        NaN
    . c0 u4 G0 A% U/ ]9 V# i7 i2 e1        NaN; \9 `. M; w1 {
    2        NaN! x* Y% \7 O6 w5 \+ {0 ^# C
    3    本科二年级学生8 W- c4 x4 t9 w# {6 ~7 E
    4    本科二年级学生) C8 N1 o+ Z/ P& J
    Name: Grade, dtype: category8 |4 O5 o( \" u$ j, q
    Categories (1, object): ['本科二年级学生']
    ; c5 f8 ?0 [5 W. k4 g" T1
      f2 ]. A0 ]( M6 }5 |2
    4 _" g9 j/ w7 q3 g+ a36 t% K) I# n3 X0 z
    4- y* R; b3 @7 i- R& J
    5
    % \  B" D6 s6 I- a- d1 o4 n; w6
    % w  _8 ?7 H; I3 y4 ?' B79 \; C; I6 W- d* y- N% X) g
    8
    , q, Y  h. ]: H  Z) h. A0 \! [4 m95 o1 w- E, A# @7 \- q7 l1 i: |
    10* \# I/ Y6 N3 Z* Q( N# ~* ]8 e+ `  t
    9.2 有序分类
    3 b" s+ k' `8 k/ h" U1 I/ F9.2.1 序的建立2 x9 o0 H. K1 T  x7 N3 ~- A
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
    & h( G' b9 o, ~
    : u& H( C, J4 O# `" Js = df.Grade.astype('category')
    - @) {  A# s0 o$ ]  Is = s.cat.reorder_categories(['Freshman', 'Sophomore',# i9 o/ s4 z/ B& ]/ A' A8 ]' L
                                  'Junior', 'Senior'],ordered=True)) l4 y+ t6 k& F1 o
    s.head(); C% Z9 x* M* _& U' m( o, b
    Out[24]:
    ) O4 z. ~! d7 V& ?$ ^; {! v/ A" l0     Freshman  \% N. N; ^! T  W9 U
    1     Freshman
    ) ^. i: E. |" n% W2       Senior* m. ]* ]2 o$ I( k% o: k+ p! ^! w
    3    Sophomore- v5 j3 [, c% a2 Q/ R( O) O8 G
    4    Sophomore
    ! t" R0 k6 E4 O$ oName: Grade, dtype: category
    ; g, _( Q7 E" @7 X2 T# ~6 [Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']5 E) W) e5 o; b4 X

    % C( m% N5 k8 z# x! K( L1 ]s.cat.as_unordered().head()
    - ~* l3 C% f  v+ |8 ], |Out[25]:
    / x9 ^4 g- a1 w7 X: A; v0     Freshman( G/ f! ^5 w( A; M
    1     Freshman% h3 H, K  g& c) _3 Z# O
    2       Senior. Y' h. A8 B, @, e
    3    Sophomore
    ; v; b- ]$ g. ~4    Sophomore: f: b% P% c! l: |
    Name: Grade, dtype: category0 Q4 J* ]8 Z' q& X
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    - I; o( b7 [! t
    8 N0 ?6 z4 \( r6 I) y$ k1- |: c' j& U. P
    2
    ! S- H% x8 A! M) b5 N: }% h3
    : `1 w6 l9 U- V2 l. F: {* J+ p# ]4; M# t& t  ~8 g* M% Q3 T
    5
    : ?8 D" Y5 E5 X$ P$ H; Z: Q6
    , T% ]5 o& t- D9 ]7
    4 m5 B* a( M1 H2 f8
    $ t) O3 a( e( t/ r& i9% e6 u( |& e" D' Q4 Y, \" Z) i4 \
    10, q) c3 R! }* P: X$ p, F8 o
    11
    " j5 V: u6 J8 n12- e) O! W% U. f9 Z# d
    13
    & r  M* _( }* |& B3 v5 h14; J: U" _6 ]; |
    15' F# a# B/ D; ~4 w4 e
    16: g* R6 [& [8 d3 |/ O  a  U8 A
    17) a, ^4 ^/ v* u' v: G
    185 _; I) d: V8 j. }  _( s
    193 i5 z- o6 S3 y+ k% W: x
    202 C, y% i( E3 n, F4 e9 D* n% L
    211 T  j! V/ E/ ^7 Z8 |# l
    22
    & [. H4 H5 l6 z# ?' \9 g9 B  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    + Z& H1 N1 A( O3 b5 Q$ S( H
    1 Q" L! p8 D) x0 k$ A! ]  l& l9.2.2 排序和比较
    - a) t# w. \# q! v1 c; G在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。& ~- T$ P. [8 T

    7 y! z8 Z' X/ M/ X  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    2 V2 N3 G1 R/ X  v, ?6 `% o
    $ J3 W# P7 z% J- @df.Grade = df.Grade.astype('category')
    , i. \( M! Z3 U7 Y  @* tdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)" D+ k4 M8 w* G3 e, n& x
    df.sort_values('Grade').head() # 值排序; u9 {. m+ ~; @2 K6 p7 }
    Out[28]: 1 a+ w) R! _4 H3 {" k% v
            Grade           Name  Gender  Height  Weight+ V0 p2 X. z7 Z3 q; I: a& B
    0    Freshman   Gaopeng Yang  Female   158.9    46.02 n$ {# A1 y/ H3 j# L
    105  Freshman      Qiang Shi  Female   164.5    52.0
    ) O( |7 A4 ~; s  A! L5 p; m! U  s+ {96   Freshman  Changmei Feng  Female   163.8    56.0, m% e# _0 b- h
    88   Freshman   Xiaopeng Han  Female   164.1    53.0
    8 S8 u8 r& Q+ o! l1 k8 ?/ _" I* ?81   Freshman    Yanli Zhang  Female   165.1    52.0" K0 c0 C: ?+ Z8 H" D2 U$ K7 U/ c" W* c
    1 ]0 ~6 v5 M! _5 f4 u2 Z  s
    df.set_index('Grade').sort_index().head() # 索引排序
    8 ]- W9 t. C! L5 n4 pOut[29]:
    & t& E: m+ s! Q' S6 W* s                   Name  Gender  Height  Weight
    , P6 d2 L" A5 U$ x" s0 ?Grade                                          # M5 I% V) m4 r1 a
    Freshman   Gaopeng Yang  Female   158.9    46.08 v; x# r3 z2 C" u
    Freshman      Qiang Shi  Female   164.5    52.0
      C) f8 {: ?2 s& [0 }Freshman  Changmei Feng  Female   163.8    56.0& ^3 v2 p$ c5 Q
    Freshman   Xiaopeng Han  Female   164.1    53.0& n6 \' I7 [  [: b
    Freshman    Yanli Zhang  Female   165.1    52.0
    6 P* W! A- k! e1 X  F% i& L/ z/ u3 h. u
    1
    ! `2 I4 e' M) O27 O  ~( {" s1 a, Q' N# |7 v) x
    35 Q5 @9 M6 f9 U$ ~/ g
    4
    : m4 H. w3 X2 `8 C$ {- d: k! B$ ]5+ @" G" v! q5 v: t. k& v9 {
    67 N+ w# d$ q  D3 O0 v
    7$ t  _2 n" r7 z& ]1 m
    82 o; e0 {7 b/ C
    9
    3 T9 Q) L0 c% O10
    3 r1 X" U6 [7 }/ \+ J6 i4 Y11
    " j. y4 I. r: ]' Y" F; ]12
    1 v! \4 C  m1 {2 X/ g) t13
    : ~/ f: C; _, v3 u& x14  L$ J" b# F% B# I, k
    15
      ]7 s6 c: x3 X  K" `0 n16
      b: J  z1 t' V0 G/ j- c) K0 R+ U1 P17
    7 M* y9 k( l: L+ W18! G% P6 l  N0 l
    192 b% p( K+ y5 ~9 U; U
    20" Q' b7 w! x8 |; i. ]
      由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    ( [8 @- Z! S9 S4 F
    - z: }9 x3 `+ Z& V==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)2 D+ U8 @6 O3 G; C% F8 X+ e
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。4 W1 \, I, {0 e4 ^3 R; c9 u
    res1 = df.Grade == 'Sophomore'
    0 G& }. k9 N+ K3 y
    + ~: s9 y. U5 A1 nres1.head()
    ! ]3 ]% T$ [) c+ @3 a7 TOut[31]: : C  v2 g  m" U0 R
    0    False+ Z+ _( Z, {% E  I' w# h! {
    1    False# i, B6 g) E2 P/ T6 |: V4 v
    2    False
    ( |! @2 d0 j$ c* Z5 _; U1 T3     True
    . N/ S; ^# q. n& c4 b4     True% ]" [1 c1 L% e4 |0 m5 T
    Name: Grade, dtype: bool$ j$ J3 T& j: _, _1 Q2 T

    / Y+ D, p- H7 _! j6 p. g* C! A8 ires2 = df.Grade == ['PhD']*df.shape[0]( B$ |4 b: j( ]

    4 T6 k5 U. K* v( M! jres2.head()( a" V# R- p1 V8 Y. r# {
    Out[33]: 4 u) S) v. B2 D. p0 M
    0    False; h! c0 q+ S5 Q: b& x0 J. r$ c5 R
    1    False
    , y& f9 K! b2 [  k/ `2    False1 X- t+ L: y$ T; V1 r
    3    False! e+ Y- g3 V" Q6 n; t
    4    False$ Q8 J0 e& ]% }9 h+ b1 g
    Name: Grade, dtype: bool. U  _, i. M+ r
    1 w7 G( y: ]& w" P# z! T- s0 H
    res3 = df.Grade <= 'Sophomore'- X7 K, n; F& i  b9 ]" s" _1 `

    2 B6 `; w& ]& R2 j' cres3.head()& {( X% m0 s9 G3 K
    Out[35]:
    9 k6 l( `/ H! C4 z0     True
    / g) D/ B* @8 e' W1 d& v' V" g1     True
    ; X# }) J; j% D1 L2    False
    4 U! ~* y4 P/ E- t* [+ ?5 Q3     True
    / b. `% I! N$ j3 p# c4     True1 J! A# M! ^, }! e* `! [0 j8 \+ |5 k0 _
    Name: Grade, dtype: bool, b3 M0 Q* A7 m7 V1 s$ o
    : f4 u5 G8 ~6 L/ e. J- W2 b
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    / V/ Y$ n+ ?/ K8 y& g8 A$ B) @res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 1 g* [; k' |! P3 S) V9 ?
    + f$ x" V2 S( P. @8 H
    res4.head()
    3 m; _& Z5 X% W: U% j, U+ V0 POut[37]: " j6 T) U" P2 o3 i
    0     True
    ' `  P; y2 \; S# ?/ a( c! _1     True
    , \8 d2 Y  u- Z6 R+ ?1 U  L8 \2    False
    3 ~) G1 v; i' y( A3     True8 j0 c9 g$ X- A" E- Q7 A3 K+ d
    4     True
    8 w3 u% e0 z- vName: Grade, dtype: bool
    " R7 o! s% c/ w% s) \( R7 E! p3 z/ x; E7 ?3 _# p9 U0 }* M, E- I
    1' h4 @1 ?. s4 V! t
    20 [4 i* r0 C! y/ O; ~' `! ]( `
    36 @, f/ p: D4 L, I0 ]$ b
    4
    0 [7 ~5 o& Z! ~! R: F5
    $ Q$ _4 g$ g3 \  }# v6
      M% z5 M9 w) `+ E5 w' M9 G4 p7' r4 c& ~; N. K
    8
    & @- X% k+ a4 ?* @2 h. B99 p0 J: Y2 }5 l
    10/ V0 }* v% y  _6 ]# `7 C4 }- K) V
    11; m; C) E4 Z- H/ H8 S
    129 `0 e0 B& R0 h  D& D' n- j
    13
    % F. h! b! H) F$ W$ Z0 F8 w6 A14* j4 W! r  ~5 R, f" h: y
    15) ~9 s7 J! `. p& B; r& N1 i4 D. ?
    164 G# d  |# {8 e: K( Q/ @4 z- N3 k
    17
    % P$ z+ |) h5 \3 p7 z18
    - f9 `9 s; D0 t! L$ K; o19  _: L5 C0 p8 Y- X- v" j
    20
    $ y; F" O$ h  K3 D) Q% K& j- S( w213 a. @9 G) h. ^& ^0 H0 }4 R' E) W
    221 {& {2 K  N* O/ r8 l* p6 m2 {
    23
    . [/ [, W: W2 o! M( F: J  j) _247 W8 \/ J, M5 Q$ A# Z9 F
    25/ \& }. q' c4 i3 {! O, [
    26* v. |+ B) T0 a, Q# Z+ Y; K. o) Y
    27
    ; f( H! x' r9 U! u& ]; J288 E1 B0 j; S7 n. n# Z4 Y
    29
    " U% ~  y. l! q3 D' V# i30
    6 a( F; O% u0 s4 B31' n  H' n6 A4 q3 u# {0 q( N2 u
    327 d: G9 I3 {4 }8 O3 [8 ^
    33$ v6 @7 c1 O6 _
    34
    / b8 ~: L$ {! l& @2 j5 l35' u: ]6 m/ A( `8 ~# v' ^" }
    36
    . V! E& u2 e2 P. I5 `$ z& z2 d, G2 v( @37
    $ b& \& Z, T: c8 B% A5 s. }' b38
    ( D- _% ~1 |% }, D  b3 V39
    * u0 Q/ k, o* o) Q! p  P+ H40
    1 v( A3 b# D2 I4 G9 G. G$ R$ j) z7 g41
    * j2 C, o: {6 Z+ w5 f" ^423 E2 f% I! I; U, w' U' W* L8 U9 m
    43
    4 h8 N6 L: q, L8 r/ z( T( D7 b8 C44" L3 a/ l$ \& ?
    9.3 区间类别
    - f) w; R* s( f8 o% b# Z+ ?9.3.1 利用cut和qcut进行区间构造, {$ \( t1 ?& m& l0 P
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    6 a: I6 Y6 T( `1 d8 H, g9 g8 Z9 o$ v1 _1 J0 V; o
    cut函数常用参数有:
    2 J; N9 m5 K6 H2 H# i9 }2 z: _bins:最重要的参数。
    * p6 r1 Y1 a; u$ Z" w. U如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    8 \" q6 @; O; z  X% W" j也可以传入列表,表示按指定区间分割点分割。- H: w, B+ q; f+ {9 w, a- J
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
    8 a3 A" f# m& f0 x) f  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。% \, Z6 H% j0 h$ A# u

    # T& A9 b/ N  s* S, J( Js = pd.Series([1,2])' i; A( l' E2 i! H$ d! Q
    # bin传入整数" A% [' K) S: H4 N
      }8 g2 h: H! I. L7 M+ q8 \' ^4 G
    pd.cut(s, bins=2)
    + H7 J2 @, }2 M# J3 m7 k0 bOut[39]: 4 A9 C6 m  C" F1 p8 ^1 r' l4 Y
    0    (0.999, 1.5]
    $ r+ O4 X2 f2 b$ u" V; m1      (1.5, 2.0]
    " F, f! D* B, ?) ddtype: category
    . r( f" L7 f& G% L0 n- j1 J8 iCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]], ]; L- E+ b5 Z& ^/ A1 q0 L$ ?
    . R2 E5 o% W; w# y) P
    pd.cut(s, bins=2, right=False)  K' A0 N' ]4 h- P( E+ @
    Out[40]: & v3 X  f+ E1 a
    0      [1.0, 1.5)" p- g0 u' B, a% r) m& ]) _
    1    [1.5, 2.001)
    2 r" K( k' Q/ M+ _* x( j# D. n  a" Ydtype: category: j8 i0 Z% Q* Q# w  v
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
      k* [3 B, j- K& \4 y2 T& l0 s" W+ Y( v7 n; ]% v$ s

    7 q3 l) D" `( g7 i$ L8 x) ?' K# bin传入分割点列表(使用`np.infty`可以表示无穷大):4 ?" S7 d+ A3 y& x0 I# v
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty]): ]% a! q8 Q) n0 C' U
    Out[41]:
    # T9 z8 p$ S- q, {  C# ]0    (-inf, 1.2]9 p' T8 G6 X6 \7 P' a3 }
    1     (1.8, 2.2]. ~" w! i5 R* _3 f2 R' e. n
    dtype: category
    8 t( H2 r- U& e6 G* V' ?Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
    3 h  B, r' G# o7 ]. Q# y3 j
    1 O  ^: f3 ^/ T: |; X0 n1
    ) z3 v; j/ h* x2 J/ V4 y  E  p21 D: I1 T* x6 P0 f, a- E& o
    3
    " g# @- Q' L; s- e7 a2 S41 O4 ]1 g) T" V9 O' D
    5
    - b4 ]! ^" Z+ w60 n& |# s% J  P8 _. i1 U- i/ U
    73 h- z* N# |' f. V4 [' K7 b2 D
    8
    : P/ D6 R# e' P* o0 W7 ?! G+ A7 ]9
    - y8 U3 \8 A2 n2 f; g# B# B8 t10
    % A2 j4 Q& v. y  O' a7 Q' p11# D# Z9 |5 L' Y0 h" M! K/ |
    12) X* d1 n! P9 X# W6 P- l
    13
    5 P0 `) m5 D& r+ ?; h  v144 f' X% T5 V2 H+ b( Z
    15
    " B% U6 c2 v( q. n6 F/ v16
    ! N7 T& U7 p& Z3 g6 L5 K) R4 M# C17
    ) g8 x4 S) K/ ~+ o18' ^2 Q. G2 M  R; n
    19+ F& T& Q! j5 W: D* ~
    20
    ) t2 A: |5 }2 v: e: h% q21
    4 i' X9 [+ ^0 o, H( Z4 s22
    " ~8 t9 R( p) I4 s23) K, Z( I1 n9 B* d9 P& s
    24
    : T1 ]; ~+ Q  B! S5 S3 M# N" P25
    7 P$ y) Q) Q$ z* R  i2 x) @  v3 ~labels:区间的名字: N6 u- N1 @4 n7 W' t  j5 ]- l! V
    retbins:是否返回分割点(默认不返回)6 E5 t: g" A4 e: }
    默认retbins=Flase时,返回每个元素所属区间的列表
    " u1 o7 F, E6 S: G: }) wretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
      Z  z7 n) j& {' w& Y; n" E
    & j9 E: L3 c" rs = df.Weight. o; P* h- P) b- `
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
    & f6 \5 g  R6 ~+ |" bres[0][:2]6 a- _1 K  L4 l; U5 c

    & M$ U. w$ u4 ~: q0 u; |Out[44]:
    3 H8 A3 n2 T. ~1 ~0 P) g. b; B0    small  q) C" ]) z7 v/ E# v
    1      big8 w5 x: P, ]5 T( l: Z% T" p
    dtype: category
    / ~! j. A+ B9 R" J; x# C1 z8 ^3 TCategories (2, object): ['small' < 'big']
    * P, F* v9 G' L+ b& m
    ; \) P8 L& ?2 p3 R* y8 hres[1] # 该元素为返回的分割点
    , W+ o" w" Z5 f# b+ _; P5 X/ ?Out[45]: array([0.999, 1.5  , 2.   ]), W4 ^$ `) P. s0 g/ P/ k$ A9 ^
    1
    , x" @7 n/ D9 h2% D" X( [  h" M  N
    3
    1 n: l; D4 _" {$ z. ]9 K% Q4
    , Y. D3 h8 c6 I. G) h- A3 ^5
    4 w/ g6 O5 B$ F2 G( m0 H6
    & ]" U# n2 [7 r1 f2 d* I7, \& m3 f8 }9 l
    8
    8 x' o% X8 v; G8 ~/ b0 D97 o+ u/ g  J8 W# @4 l
    103 L; l0 A% e  \
    11" ~' \2 K/ \" p$ ?4 C$ p7 a5 M
    12
    $ I8 s% A: k* ]" a- c  j! \qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    ; z& i; J+ s# o1 @7 [8 zq为整数n时,指按照n等分位数把数据分箱
    8 A( \& n) a; Lq为浮点列表时,表示相应的分位数分割点。% _: _* ^5 y' M' B( y/ f9 Y2 Y
    s = df.Weight
    - K/ D1 a( v9 c8 u
    & C( {) {, a- E/ X) Epd.qcut(s, q=3).head()& D0 {0 R' ?# M
    Out[47]: ! e2 L. w& H" y$ W& G, S+ @3 O
    0    (33.999, 48.0]
    ; Z" K9 w- @2 D  I6 V  A1      (55.0, 89.0]
    + }' h/ j% k( I- A1 h* o( K# \2      (55.0, 89.0]! Z4 q  ?$ q% u, I6 x* N3 P
    3    (33.999, 48.0]
    9 F/ _& }5 K  `1 l) E# S: D4      (55.0, 89.0]
    / |& [* k, f& v* zName: Weight, dtype: category: `. k" w/ w( F2 `
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]+ ~4 C* a7 ?/ `+ G/ }0 w

    ( l, T8 ^  E+ m# U5 P3 Upd.qcut(s, q=[0,0.2,0.8,1]).head()
    * l* i& `* q0 B& v$ f: S6 jOut[48]:
      H" _2 U+ D$ J" r: T6 M0      (44.0, 69.4]8 d# [, b0 z0 _2 R- a  Q% |, ]1 [
    1      (69.4, 89.0]) @# A  k) G7 ~' k4 i. ^
    2      (69.4, 89.0]2 u1 j7 v; x2 |5 s, k0 P
    3    (33.999, 44.0]
    * H+ I$ B) K1 m1 }8 Y) N8 q# ?4      (69.4, 89.0]
    $ Y; R+ `3 y& j) u; rName: Weight, dtype: category
    * v- `6 O* ]) KCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    6 ~, }: U! _  T$ D. w% j8 H* ^9 ~# C, t: W- M
    1
    % s) m6 j7 ]& |/ k# Q; o8 r2
    ) l4 S6 n5 ]( A3# @4 C. v! I( X5 m
    4. i/ G+ E2 ]2 q
    5, p9 Z3 [! ^; X4 B9 F% d' J4 @" A; {1 S
    6
    2 P! u5 T: I/ ^- h8 C1 A( L72 V4 f( v+ E- ^7 G  h  [* L* \
    8
      j% h' D: j. e/ }; H* Q0 C* o9
    & ?1 F/ r! H' H+ i6 Q% h10( M7 p6 X- ~# C( W( C* [
    11, ~0 O! Q. D1 S* }) q  ~) B5 ?& w6 o+ H8 c
    12$ z2 y/ v- q+ _% g7 j2 i8 L7 {7 v
    13' B9 \9 L7 G4 l# L; b) S% A
    146 i. f/ u  f, F) i3 ?2 p
    15
    / \, J. u4 l+ x) A  I8 j16
    : S. @& d. O) l) l# y7 ~17
    $ W, @4 {- V8 [189 s! }% J0 G6 v7 r# G6 `8 }& [" ?
    19
    8 [9 Z; z  K- f20; ~" g8 n! G9 k9 b* V
    21
    ; f# k% s* t( H5 p  z5 u9.3.2 一般区间的构造5 [6 |% m- W' r% t9 z6 w
      pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。* a% e' Z% f/ @. O4 q
    ; V) K) E, ]1 E
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    ! s2 n0 I& a+ ]3 S; v/ _# ]$ dmy_interval = pd.Interval(0, 1, 'right')
    * ~$ r  D7 @  n7 b! E" k7 {/ Q- P! u5 H2 Z; s
    my_interval; Y8 I& h* d9 T" n% ~
    Out[50]: Interval(0, 1, closed='right')
    0 n1 U  @- P5 e  b+ I* d+ K1  _0 j: H) F! E
    2
    & f3 R, K7 D6 D1 D7 r4 i9 g0 t3  j0 V- p$ l6 z6 H! g* f
    4
    1 s. s8 X0 s/ Z" t8 p, O区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。) r! u# B! M2 T* K- N4 s2 e
    使用in可以判断元素是否属于区间
    9 q  b3 ?( W( H5 ^' P3 ], x7 i用overlaps可以判断两个区间是否有交集:
    0 x: D, e1 ^8 D4 [0.5 in my_interval
    % A* r# x# J3 L* P; S# s7 y% n9 @. G# Y( Y) T% q
    True
    : _9 A+ E% Q: o7 u; j0 a/ p. c+ [! `1( D& p6 p- @$ c* i, m
    2  E% o  i% E1 r4 K- u
    3- b* f) U  B, {; U6 ?
    my_interval_2 = pd.Interval(0.5, 1.5, 'left')
    ; Y9 O; p0 C8 D: f( `my_interval.overlaps(my_interval_2)
    . }9 x; a4 s* X1 K9 A5 T% P
    8 G) I5 b/ _  V4 }& t3 STrue! x, \) `2 }* Q) z% y
    1
    * R) h+ P5 O9 j7 J9 v$ u$ Y4 W2) q  a5 K2 N, q# ?- g6 K
    3, t( x  S# Z! [. h7 v
    4  q5 S, r- s8 g+ ~
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
    8 T; J* G& E5 U
    ' J% W* M9 t# A; g* _/ l# W' dfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:$ e5 \; r5 p. c& G' g  j% M
    pd.IntervalIndex.from_breaks([1,3,6,10], closed='both'): I+ x1 s9 Y( b7 T9 n& M5 V( I4 B
    : j/ Z1 t1 K. q+ Z8 }
    IntervalIndex([[1, 3], [3, 6], [6, 10]],4 ]1 G! `/ M) O/ Y$ I
                   closed='both',: G/ m6 ^. F9 b+ p- |& b, B+ Z
                   dtype='interval[int64]')) `7 _+ f. w2 M
    1% _! ?2 Z/ J' o7 n& w) N3 Z" R% s7 Q% l
    2
    & ?' S( u9 K# k; ]3
    1 C! c7 m) @* O2 g: }4$ K. v  u* Q! g% b8 m, n
    50 I0 ~1 D4 e/ ]) i$ ?
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:* ^# L- u! K) O/ A  o" U1 t; P
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')) Q9 m5 x2 {" i9 y

    8 U. V7 I  d% a2 w0 dIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    ( y4 b  W7 g+ Q+ U1 A/ d6 K- L                  closed='neither',% {( U3 p- l: S+ H7 K, x2 @6 e/ @
                      dtype='interval[int64]')8 }3 y" _  }' y0 a; z* i2 q
    17 n; I6 _6 P8 _: `6 f) l! c/ O  n) {0 L
    2
    6 B# O6 P" ?8 G. ^: l' J& w3
    5 c! k1 C1 q. U4
    % {1 M( H( h% a4 D' Z* U5+ O$ V! E+ @  f9 M4 i* t, H# D
    from_tuples:传入起点和终点元组构成的列表:
    0 ]/ j! v" a! N/ Apd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')+ u$ Y- j  h! n# J" E

    ( _5 r8 ]6 t7 n$ Q' `0 s& l- RIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    $ k: `+ M- q8 i; W! s5 [              closed='neither',! ]2 t6 o0 Y0 q( g. `* f) A: R
                  dtype='interval[int64]')
    - j! j8 |) Y. n1
    9 V* i6 k7 F. l. O! C9 ^20 p( A. f, M& v" G0 o
    3
    + O6 V2 D' [4 g) a4
    ! |8 j' K( w2 x% A5 @$ ~5" ^$ O7 j5 l+ }5 C4 U' \6 F% P
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:. Z# s3 b  O/ t4 x
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数" e. \7 E+ s6 \; S1 n
    Out[57]:
    ' r0 p3 G- V8 DIntervalIndex([(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]],+ Q  z& t- {& N  x3 I9 s; Q
                  closed='right',
    $ Z. A0 S/ q; @( ]4 W* s              dtype='interval[float64]')
    9 G: Q( r4 f4 X) ]8 e
    9 R1 G% ^# y4 `, q# kpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度& l( J) w+ l7 O( e* z! n2 p
    Out[58]: - A3 P2 Q: M- C& f
    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]],
    3 T. `0 f0 c7 E, |" z) }2 Z: \              closed='right',
    9 ~; ?! Q; s6 w7 X( d3 A8 C6 Y8 ?              dtype='interval[float64]')
    , R2 b1 a  F9 L3 b! L0 L1
    : R5 H" S( G  q# {2
    0 r+ l, {8 G% G4 m31 R' q) M# m' y7 u  Q' f3 \
    4* I8 e# l& j" ~5 u" s+ f
    5
    9 `. z1 h7 S$ w6
      I5 |( z2 \# S) n5 \# ~8 n7
    ( z8 S% F5 j7 _1 V$ a% H; v8 V* N8# d% Q, R7 {* a; C
    9
      l* M8 O$ P0 V+ M10# j( p. @' n1 Z- _7 Y0 K. A. a6 c
    11
    ( J+ l: F) x5 b! f1 L' O) d【练一练】, l# }) u9 n5 B/ r
      无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
    " y6 u3 Q8 w/ w* }0 F8 ^% x8 j2 X( d; q( \+ o* U
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
    0 c- W' _8 v2 _& |: L5 p, g  \$ V1 ~$ \
    my_interval" Z4 G7 [1 @( T) K. L3 Q/ Z
    Out[59]: Interval(0, 1, closed='right')1 I4 G3 H+ u7 A  ?
    + z- I) i  W9 U
    my_interval_2
    ( u4 l; L" h; WOut[60]: Interval(0.5, 1.5, closed='left')0 P" G# I, f$ \0 B6 X) M

    2 O/ B% D/ R: Q- y' zpd.IntervalIndex([my_interval, my_interval_2], closed='left')5 _( x) N) S  S, F' }: |% k0 F
    Out[61]:
    9 C, @5 }" t/ I# V. P  f/ ^IntervalIndex([[0.0, 1.0), [0.5, 1.5)],, j1 m" n" r% o; U8 L4 ?
                  closed='left',
    6 ?, }0 f  a& s! x7 J              dtype='interval[float64]')
    . J! a" E7 s) j; G# k" V- D  ]) k1& S, H! Y9 m4 x6 O) j, S0 s# C& ?
    2
    ( o3 |5 U/ g" ^9 l9 p3
    & M- t; k% h" N" U( F2 V4
    + Q! C9 w  Z$ f- X& [9 ]: P# g5
    - m6 o$ f- ?8 l' y% f8 ]1 e/ g) v6( j* Z. Y; \2 b# y2 {6 ]
    7
    ' V7 v, K% d9 N" D% X87 \/ ?' ^: M! g# A: B) r
    9
    7 o- x1 y" V. p6 |6 s: {108 R0 B! T* _4 r+ k+ }7 ~, n, |2 t
    11
    ) f& j) w8 I& G9.3.3 区间的属性与方法
    6 V' \, n6 s/ g  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:: m* |6 r( g/ B& P$ V
    3 I( e  N2 ^% u+ k( j( L6 g5 t  a" `
    s=df.Weight/ \* q6 t  ~6 M$ b8 d- l3 {% l
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示3 z- n7 Q0 P7 i/ ^) b  j
    id_interval[:3]
    - Z$ t  K& T& F8 x3 w3 [7 m. v* h, \# c, N( f" |2 k
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],. M  B8 T  @. l3 M* n
                     closed='right',
    1 k; d' ]! N# D' u8 k                 name='Weight',
    0 X5 e; w& ?' b' a                 dtype='interval[float64]')
    6 i# M7 s- J. \9 S9 E12 t* c% N  W/ D; `5 Y' Q$ a
    27 a/ X3 E! M. i& S! I
    3  G( A" m5 i3 f  R6 g4 o
    4
    ( W- @* k( c3 u  R- P. H5
    7 ^' ?; g9 N1 r2 N6% J+ b6 }1 _# k, K, h2 v
    7
    4 k+ }2 \& N$ z* M# ^% X; @* y8- S$ x# r+ d+ u  @5 |2 d
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。0 ^: M+ @" S$ Q% o" _6 G
    id_demo = id_interval[:5] # 选出前5个展示$ Q6 f: n( E9 Y( m+ A

    7 p1 A( t1 ?3 `3 k2 A1 a9 }% e- Pid_demo. @% ^+ A; I& [  f4 v3 ^5 p
    Out[64]: , X8 n: u0 `. R; l
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],6 Z( \% J9 j' T( }
                  closed='right',
    & c7 a$ i* f: \7 M              name='Weight',
    ) j) W( H/ c7 B  G1 _' [+ v              dtype='interval[float64]')8 v2 A2 {0 A7 W, i' C' Y! f) D4 \
    . q! f  h, ^: i$ ]
    id_demo.left # 获取这五个区间的左端点& {  O. Q( [1 Q0 E
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64'), X4 e+ l# r2 ]% A; j, D" k
    3 ^( E" L4 k/ E( t
    id_demo.right # 获取这五个区间的右端点
    , `+ X" l1 ]; _  `Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    # ?3 z+ d! p7 r- D5 }3 Y
    , M! j1 X5 t; [* e- Hid_demo.mid( K4 p, p2 @1 ^2 D# t
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')$ I' ]2 c" `. K! J* ]1 B. o6 J7 U+ P% n
    6 H/ X9 R. \1 e/ W; h
    id_demo.length' L$ M* j, R# P/ F, S7 z
    Out[68]: 2 R. I( E0 z) h
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,) p$ ?& v9 ^$ F$ I- i) K
                  18.387999999999998, 18.333],! N2 `8 |! D/ }% @
                 dtype='float64')
    1 w; |5 F1 j' O! B0 A9 }, G' G1 c7 q4 m( z  k; _' W; E0 y
    1
    : A% J# ]' y& R7 M# j: S1 S2
    ' {% c# g# o" r: y7 r1 [3
    2 ]: k7 J, K- j, L) f; Y% J+ u4
    8 z1 d  J& {6 W7 r/ S/ j5
    7 R% O; b; O  B+ @6
    . l6 b' A! t  @8 V1 d7
    $ i8 j4 ^- j, e86 d  d3 K# Y8 |+ A
    9
    - M$ ?% s7 I! o! ?10- q# X: K% a7 t9 U' X) |
    11
      q, L7 {! h, s$ R9 N! i# m, S% h12
    7 n. }3 j3 ]4 s. i! w* i13
    & H+ P. @) ^, [' ~' i$ s14
    $ a. v/ ], e8 E7 Q155 L' c! ?; N, d$ |# r8 y- ]
    164 U, ~! o" U( J! S! v
    174 ]# _! B) h2 E' r
    181 c4 F* R. A8 M6 `
    19
    + x! K: i: s2 r6 o! q202 e- j0 F  b1 {+ w) S
    21  t; w& i5 z; r2 H$ i- e0 u
    22
    ' ~' c5 p* ^- S) t" p+ R' T23
    ' F) S3 ~6 r' U  S! x" AIntervalIndex还有两个常用方法:
    ; `+ t. N3 W5 a+ L3 @7 Gcontains:逐个判断每个区间是否包含某元素' Y- g% K. x6 f$ a; M
    overlaps:是否和一个pd.Interval对象有交集。
    " w( X3 Z# R$ f8 S4 ], n- Pid_demo.contains(50)
    * E8 {7 f/ w3 m$ J# XOut[69]: array([ True, False, False,  True, False])
    : e4 j: ?8 y' \3 s; P3 u8 ?
    ! L4 F* e/ n0 u( L1 Oid_demo.overlaps(pd.Interval(40,60))
    1 i/ i, N4 ?* n6 I- B3 s" U0 c! j& JOut[70]: array([ True,  True, False,  True, False])/ \( ]! g$ d( A$ V7 v6 J' l
    17 L4 H5 v7 ^8 `- ^! S
    2
    3 h: }/ c# A9 u; e' F2 O  s1 g3
    * `9 C$ C* F& K! G) d# N5 ]2 ?' [4; U* n$ T' c! A6 V5 `' h1 w! G6 Q' O: c
    5: N" K# t# S. T( P% c
    9.4 练习' B) f( L( A1 d7 H& {
    Ex1: 统计未出现的类别
    ) L1 ?: d5 H8 f( F) C4 B, v/ z  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    " {3 [4 d& d  r' `1 J  p* w* a/ G; l( N6 {
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}); _7 {/ B9 h1 E
    pd.crosstab(df.A, df.B)
    5 u) P8 V; v, v9 f/ [8 R+ o* L* q8 x+ B: z8 A. @
    Out[72]: 6 i7 J" A5 b; `& h
    B  cat  dog; @1 d" q8 y' Q$ h$ G4 c( z3 U% [! f
    A          7 l) u% Z& y9 Q1 [- p
    a    2    0& E) }. K4 H9 Y, y% m( L
    b    1    0
    9 ~! l( V# b' ^( w" L! Tc    0    1
    0 k/ m/ Y- a$ [4 B% u12 e0 t  @- U4 @; k6 G7 S
    2
    " k$ z3 b( W9 l6 a2 [7 ?" e$ [4 x) S3
    # i' N+ K) t% k8 F2 o9 A* a7 q* f4
    * z1 ~* x+ [( _' G8 T5
    , \/ m0 Q8 Z% I1 l0 H$ r) v6' s& G. S0 ^/ i1 ?8 F
    7
    " N6 K. e: d% ?8
    # L) f8 z' p, B1 P% ~8 k% A$ G9
    1 G6 H' N  n/ Y! O2 z  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
    , C! L1 y9 z4 ]
    / P% a4 w' d( adf.B = df.B.astype('category').cat.add_categories('sheep')2 v$ h7 X) R( J% h
    pd.crosstab(df.A, df.B, dropna=False)! k' O2 s; z9 Y" G, E1 B1 c) P
    ! ?/ k+ a8 g: q: z" Y. |1 u
    Out[74]:
    2 @) |4 r" i7 i/ O- l4 e3 FB  cat  dog  sheep. b# k4 J6 N; x+ ]
    A                 
    * @- m& u5 f+ R+ g4 d' ia    2    0      0
    ; g( K) Y( p- V# B0 gb    1    0      00 W# p4 r" O( u2 [" h
    c    0    1      0
    / N' H" f; X. }+ A1 ?: ~1' x5 l1 E9 U" K) x  k  ?/ I
    2
    : R" `# }. a% e" s3/ X2 }: q, p4 H5 u8 t8 p3 E0 Q/ G
    4
    # q6 ]" z4 D; c7 P8 C: [2 ?5
    2 u5 U' G' c; t( |3 g0 @6# I2 R2 |$ {% B4 @; d7 C, c
    7! Z; Y8 Y# ^+ M7 E1 z( D
    8: u. I0 O+ B* h
    9, l8 P% _8 p& d) L0 m5 H* _. h
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
    0 c8 k/ y, U; T" }- v7 d+ E/ w" }
    $ G8 g4 T! d+ ~. Y6 z- ]* `Ex2: 钻石数据集5 b& v9 c/ [) K: `3 R7 N
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:' O2 r3 T& b& \5 W5 T8 `( p" Y3 L

    ; ^) a; e2 k5 M( d% S( ?3 {9 Sdf = pd.read_csv('../data/diamonds.csv') , ]( G) r$ N' r7 a7 H
    df.head(3)
    * c& B4 Z* G1 i( o9 e
    " K# W* h5 S6 \9 l# _: ]1 i5 AOut[76]:
    3 g& d% }; r) ]   carat      cut    clarity  price
    6 s8 s: k$ u0 Z! O& R* J! z. H0   0.23     Ideal     SI2     326
    / A4 t: _4 H4 s# U9 v( J1   0.21    Premium    SI1     326
    ) z* t# E  I5 y2   0.23     Good      VS1     327
    5 |/ K0 I# x4 l7 d5 c8 n18 E& O& h. D% X/ V8 N
    29 p6 O* R( G' E$ a% t
    3
    ' q8 M- w6 H* _. ]: Z9 |4% L# u/ s% y+ D1 H1 a  p) ^3 I
    5# z0 I# `8 c# c6 B; |( u
    65 D+ v' L" O8 k
    7) [9 U; Z, ?$ J7 ~% j% c& ~
    8
    0 M4 W% P" `7 s3 x, T  {4 u分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    8 J( V$ A: M4 I1 x8 Z4 T$ s" T钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。. B8 v! l" s! R) ]0 }! W
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    4 K% M; G' z9 c" ]6 l; T对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    # h  u3 F, M: Y: w( T第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。+ ]4 j  t5 d9 g3 m6 {  O' m
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。1 G5 a' P( W( ]* G. n6 ]1 R$ ?: t
    先看看数据结构:
    9 N5 n/ p9 v! L' g& M( a" Q/ \
    % R/ X& |* _& J* Idf.info(), o$ u+ S3 v3 _
    Data columns (total 4 columns):
    3 F  |2 X* {+ j7 ]* o2 c. K' w: \; ~ #   Column   Non-Null Count  Dtype  / \0 _$ Z& Q. O
    ---  ------   --------------  -----  
    & \% C+ K, C0 V7 B 0   carat    53940 non-null  float64  v2 R6 q7 u% e9 }
    1   cut      53940 non-null  object
    2 n! |" O. W( ~$ p3 c1 N4 ` 2   clarity  53940 non-null  object 9 ]8 H) M6 |" @# g! p: E
    3   price    53940 non-null  int64  # ~2 ~: ^; l- [9 j# q( s0 S
    dtypes: float64(1), int64(1), object(2)/ W- y; a6 Q" c  |& J
    1" m4 z8 t8 t! m: z' s/ e$ [0 f
    2
    & k) j% F& H2 P3 m3& o3 u' p  T$ |4 R$ F
    4
    7 D7 Q5 b- R+ Z8 |2 L5
    # I8 |7 n- U9 s( A- \5 d/ f8 q65 _- ~+ N3 Q2 }/ e( S4 Z
    74 x1 @3 x# }- O! T7 `: j! t
    8) T2 b: d3 `: \  |$ f. _4 R
    9/ G. g' k3 F3 X4 N8 K6 u9 G
    比较两种操作的性能
    . A; o& q* ?$ z  H( o8 N%time df.cut.unique()
    0 b, C3 e7 j$ A5 Q+ R& ~, B" V8 |6 W  `9 A* ]7 y
    Wall time: 5.98 ms; W+ b, u( V" Z/ Z
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    3 G; v" P5 ~# [' a- r: Q! s12 l  w+ C, _2 b
    2# k, R+ g7 D* a: R
    3) m* c  p/ s! I# W; P
    4
    % ]9 ]1 m4 v8 G7 P; o+ Z/ w1 d6 U%time df.cut.astype('category').unique()1 n( p- h* U: H6 X1 z- m& M( N8 l  H

    ( n4 h3 @  h: IWall time: 8.01 ms  # 转换类型加统计类别,一共8ms8 n1 F+ `* N3 s
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']( z8 s: J' T2 c* W; t/ d; P) k2 e
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    1 I6 M7 z9 P8 f4 X14 }9 m4 x+ I  j( W
    29 o# @( }2 O% E
    3
    4 w: m1 c6 N) e  a& ?9 U4
    # f0 g6 [! D9 u, \! w- r3 |- r56 D0 U4 R' u& ^1 u0 a  w
    df.cut=df.cut.astype('category')
    8 F: ]: K$ G) p. [%time df.cut.unique() # 类别属性统计,2ms
    1 U3 v- b7 y( ]  b" V2 k6 |+ H3 f* n; B6 I
    Wall time: 2 ms8 c/ t  I. ]! H! D
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ' s$ a. n0 k  V& t7 t. J) y0 KCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ' I( D: J! _# [" A1- H; W7 L0 }/ e6 A; M! `3 R
    2# p; L5 ~& @9 Z) L
    3
    & Q/ W  `# _1 Z4 M3 r( t4
    $ X3 d. |6 X& D5( b& _- S$ b% Z1 V! M, W4 v
    6- h) @0 V1 b" Q5 e7 C
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    5 v% i. j. L# g( y- W! lls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    # S( r8 j6 L& r% Z# Y- tls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    8 r3 y5 [0 z$ \) p. f8 edf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换" E. C0 N0 Y& q- h' c6 g$ s% W
    df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)- I" Q# Q% X1 }0 p+ y2 x; K

    3 y& _/ h  i+ S! P+ w! O/ }' P' Kdf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)  M1 R0 _$ F  [* r- I% \

    4 y6 c! K) }, y) {! A* h        carat         cut        clarity        price' k. _! n: d* i5 ^1 l
    315        0.96        Ideal          I1        2801: q/ \8 {( l. W) s6 I7 Q% h, e
    535        0.96        Ideal          I1        28269 `1 W  B6 G! b/ H( B
    551        0.97        Ideal          I1        2830; n3 J* G$ c# v: Z0 _  J" C
    1' ~  V0 w" S4 Z9 {
    2
    1 j. g. O. Z$ u; Y! H, b2 Q3' Z# a" ~! t5 R8 W
    4
    * k( V& r+ D6 h% L% e- i59 v* @$ z2 j# F% ^) `
    6' q; S7 x( b, }; I
    7
    ; o; w- w8 ]  V! J. i8
    . L; `  Q" `- a9
    ( b! a9 y& t3 n+ a; e* v10
    : S; \6 _2 d+ f# e11
    6 n$ A# z# G) p' @' W* C! c9 s2 q分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。' o7 X, c% `- p$ L1 z. \6 ~
    # 第一种是将类别重命名为整数
    ; W5 B2 P2 J8 r+ I6 Udict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))( C1 o0 ?' O% S! w
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))4 s2 A) j1 w+ t, r. K
    ' f0 l- R( y4 R0 {+ z/ [. f
    df.cut=df.cut.cat.rename_categories(dict1)
    7 |4 e7 @, z; H$ z% k- Xdf.clarity=df.clarity.cat.rename_categories(dict2)# b  S" h& M2 r
    df.head(3)- O/ |4 s5 g6 f0 K3 W
    , H0 A- j' A; g5 T: i6 w  a
            carat        cut        clarity        price
    # c  @  C5 j' |' s  y0        0.23        0          6                326, m' W/ a' y  n+ S
    1        0.21        1          5                3262 R$ F; S: b$ R1 L1 b' ^/ m4 B
    2        0.23        3          3                327
    . A9 N  s) R) i/ w% c+ ?, c1
    # L8 V( Y" d2 o$ P5 h2
    1 {9 v8 |% x7 B4 W  e& H4 o3
    ' }- @! S  K1 U& h; ?2 l4- \" V! h  \( Z* j1 V% @% m( x0 }
    56 `  u; I& ]  W7 B9 S# O: K
    67 B6 l- O6 i4 i* z! e
    7% a- ]* y* _7 p, {7 T7 m' Q, E
    8
    ' V/ Z  M, y6 c+ L1 v4 V) `9" ~5 J- S1 w2 [+ y" u) C3 c
    105 r0 o' c  P! m( G0 m0 |" |8 s
    11' s, V9 J- L4 G; s7 `
    12
    : a/ L+ Q1 r' q( h1 y# 第二种应该是报错object属性,然后直接进行替换
    - o. a6 r6 e. @$ c5 f7 {# z- s! kdf = pd.read_csv('data/diamonds.csv')/ Z  V, k3 ]9 t: l/ E5 C
    for i,j in enumerate(ls_cut[::-1]):
    : E0 W0 `, i7 e3 ]2 b    df.loc[df.cut==j,'cut']=i
    . w- }$ N! i* }3 G: [$ j" e: \2 V' e$ c& Q: Q: [1 b
    for k,l in enumerate(ls_clarity[::-1]):
    + |1 M/ m+ N! N. C, D' C3 u7 f    df.loc[df.clarity==l,'clarity']=k5 _$ z/ Z: B" B' ]% s" [; p
    df.head(3)* m5 m) R: _5 N. `, q4 y9 E: ~

    ' t4 L9 ^. t' M; ~4 E8 T3 ]        carat        cut        clarity        price  {$ F; H1 n# Y0 x1 l
    0        0.23        0          6                326
      G  |3 W0 y6 e8 l" Z1        0.21        1          5                326/ H. _% v6 E+ ?
    2        0.23        3          3                3279 N, Z9 u' P4 K2 o+ N
    1
    / z% P! Q; i+ f2
    9 d8 e2 j7 [- |3
    & T; d5 \, w( |# P47 n# ]& I' d$ y0 H0 ^$ b( w/ i6 i
    5. B2 |: N$ f" T  o
    6
    2 M' S2 f2 [2 i& x' i. |) f7- j5 \6 E5 y7 a3 T+ T* C6 J1 J
    8
    4 E8 k4 D6 O& l9% h5 k6 m2 s" W
    10
    9 k# K& z2 a3 `- Y0 O- v114 `6 q7 ^6 y% Q+ ]: n8 @
    12
    2 X+ s/ z9 S9 z+ c5 ~3 q% F13
      {  s1 t4 z  T6 U& F. [# d( Y7 P0 [对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。2 l- b6 U" _% k+ q3 G
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点4 p: d; K' u/ s8 [! e
    avg=df.price/df.carat% s' t  g5 w! W+ d, y

    8 U# O" a6 A8 c3 `' a+ L+ Cdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],8 @7 l& Z/ K* m8 d7 B
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]$ k. b- G% }& ~5 Q
    2 |, r$ G% {* ~. @% ^  f, I
    df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],, |* z$ ~& O* O6 h. H
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    1 C% }" e( G: edf.head()0 M; u$ U0 j& y; M! o

    ( `. \/ o4 t% \. o5 B) n0 c        carat        cut         clarity        price        price_quantile        price_list0 j# o% @9 f' C' ]8 x
    0        0.23        0                6                326                        Very Low                Low
    3 N4 d4 I9 H4 \$ ^) C: V1        0.21        1                5                326                        Very Low                Low
    3 w/ O5 x8 e1 x" c, Z2        0.23        3                3                327                        Very Low                Low, d" w5 P6 V  @6 s! x- g$ H
    3        0.29        1                4                334                        Very Low                Low
    4 e) v! i1 U+ I! b1 ^: G7 U4        0.31        3                6                335                        Very Low                Low                                       
    3 }: i6 p/ d' _! k- ^! j0 Z& V1 F3 w7 X" @7 V: b' o/ v
    1
    ! B! ]+ ]9 S( c. J+ s( F: P3 k27 D, W' M- A( K5 @+ x, S
    3' `7 X' `( J8 i& j. Y2 y% h* M
    4+ @5 m' Q4 k4 T2 |) ^
    5, Y/ J( f; q6 \0 p$ Y5 c4 D
    6
    4 y- I" X& f, H$ J2 F7 w7. N, Q- {% }6 i& P
    8* i( d! o$ V0 Y# ]
    9+ V: }( q6 G7 t2 v3 e* Z
    10
    / ]0 }% X: k  t8 v* j; V113 W. v/ J& }6 i' N
    12
    ) P0 F  a6 b5 C- T13
    3 w' H3 j5 ]9 p' Y. V1 c14
    : ?3 C( S4 x+ s* ^/ q6 q$ Y8 o2 T+ s$ s15; ]$ A, D# k: B5 }
    16) _5 [) g, G& {
    分割点分别是:
    ! a) h' j0 }* e2 q$ Z$ E! O$ u. F+ R
    array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
    + M; e, `! @5 o, ?/ N7 Q3 Iarray([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    * g* w6 x5 u) x1' h5 J: u+ E& A6 v5 ?8 O1 [
    28 M' V+ t( V9 a& S: n
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。) [. Z& W8 j: Q3 @/ b8 n, P
    df['price_list'].cat.categories # 原先设定的类别数
    - c! X1 w% m0 Y% aIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object'). H9 l3 {) h5 e* g7 Q0 n

    4 ]* j7 y1 z- s8 Pdf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    * V* S$ l+ R" u/ N1 `Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现# K. F1 @1 I. h% F4 O$ v. S
    1
    % e6 V/ z5 G; s# |( ?& v! }2
    1 V( s, m6 ~  d0 q4 A3 K3" I( x+ q7 \' \3 Z0 B
    48 ?/ T8 N* n- `; K9 u7 Y
    5# R" r% U9 b) O$ _8 U% z& L
    avg.sort_values() # 可见首尾区间确实是没有的
    + T% |4 J5 O! K! {3 R31962     1051.1627916 h# [" v4 P1 [5 C7 G
    15        1078.125000+ r( J8 D5 P4 q
    4         1080.645161
    9 F6 y. I8 d# K2 [  T) H28285     1109.090909, j* t/ U7 s8 A% L0 t3 j% U8 T0 z
    13        1109.677419. N& X- ?; g! {
                 ...     & f9 z+ C! h% T2 |- a4 [7 a
    26998    16764.705882. h/ X7 G) l4 i1 q% I3 S( ?6 n  s
    27457    16928.9719631 l. Y" a: W9 l  b& ^
    27226    17077.6699030 P# `7 J# c& I2 ^3 S3 V
    27530    17083.177570
    * B  f( ]3 `. [/ j, u27635    17828.846154
    " _0 u) p& v# U9 n; x( }/ p1
    0 d: n' l2 z. q3 U; {- U. R3 q2  P. ^: k: o9 y/ x4 @) N
    3/ E, N4 D" j$ Y
    4
    6 C1 B# O/ t5 V+ G' \6 ?- F" G: H5
    - ]; j8 Z+ M4 X: o6
    4 y  R# O& ]7 U7 G8 s7
    % K$ }$ m8 e- D4 S8
    8 E2 L1 R  f5 t9
    3 i& A) V$ Z7 \10
    2 p: |* U1 ?3 r# _( w( B' {11. D* H( A3 o# k5 e) a. q; H- y; c* ^
    12
    0 m  T8 Q/ v- g, p( x  X对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。2 ^9 U, n: P$ C- q# q+ b' V
    # 分割时区间不能有命名,否则字符串传入错误。& F. B2 J. ]3 S7 y9 K9 l  G
    id_interval=pd.IntervalIndex(
    " a% T' ?( q0 M    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
    * [; Q4 Q) E! e                            )$ V/ u: Z, K" b6 _, P  b: w  C
    id_interval.left
    3 i- ]( S5 r; j9 Pid_interval.right
    ; c" k5 N9 n& B* v* b5 e  Gid_interval.length                            $ b; Q3 ]+ ^7 o. M0 Q
    1
    * A  b* m. G( t+ D& J. X# d& k2
    2 M; ]5 p$ C) P: Z  Y5 p' l, f6 u. B! B3
    : \( _* c3 Q& ^; U7 E43 Q, H+ @2 {4 m1 z$ b
    5
    2 _6 C  C3 p- l! Q6* W: P4 l0 j; \1 P8 h: E
    7) K6 d/ k* Z/ X% p2 f$ P! w
    第十章 时序数据
    * y& n/ b/ B. I& H% v; {import numpy as np4 ]) R, a/ M1 _
    import pandas as pd& ^" P3 ^  B  O/ e& L' \
    1& M3 f6 p1 c2 u0 f7 F
    2
    7 t1 O. Q, A) O1 e# d
    2 ~  Z. a5 t) k0 n4 |/ q- G* P3 |/ F2 ~! q
    10.1 时序中的基本对象
    ' W% I' }$ i8 v; l$ \0 |4 f  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?9 R' x- t- X; y( w' \0 {
    ( t$ E. B* x4 Y7 m
    会出现时间戳(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的简写。3 @' ~4 f: ~8 E
    - \- q( q" M+ i: _/ ]: n
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    3 ^; C8 E- d: a4 j. ]% G% t" T2 l# E. C/ H% ]6 n: Y; E3 K0 x; ~! k
    会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
    1 w# |+ V1 w$ z$ s- E  z' v
    " Y2 k2 K9 ^3 L8 t# ?$ p会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    * }( A  Q# Z2 b5 E
    ' c0 a/ ^, `' I8 d9 @: N" |0 f  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    , B6 C/ K( B3 o- Z7 T, j" X$ G8 `" o, ?$ k1 T# }
    概念        单元素类型        数组类型        pandas数据类型
    $ k; A# C) }7 A* r* XDate times        Timestamp        DatetimeIndex        datetime64[ns]9 j# b  o( x; s8 S9 K* \* T) U4 I: q
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    * H; y& ]  k# ~' M* e1 V  TTime spans        Period        PeriodIndex        period[freq]
    ) |' O1 d, h. d" _/ |; qDate offsets        DateOffset        None        None6 _5 Z7 \& [& M  N# J5 x
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    ) }% j' d$ l7 E6 d9 V8 }  V
    4 `" Y, K: p( P+ h3 g' ~3 {10.2 时间戳
    2 o8 `2 Z% Y' z10.2.1 Timestamp的构造与属性
      D( L. d! A- _( r单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:% O3 l7 C$ P- _8 Q: S( B* M# r5 k' k
    : l1 N" J$ x1 _8 ~
    ts = pd.Timestamp('2020/1/1')
    9 V0 n% m) |! X8 w7 R* i+ J6 \8 n, y) I1 T+ F
    ts
    7 ^, K6 E4 e0 A# y/ a7 o  u' o9 _' {Out[4]: Timestamp('2020-01-01 00:00:00')
    " ~' @' |3 D$ _6 w! X* e
    ' o  G" c2 \# _  R' O+ Jts = pd.Timestamp('2020-1-1 08:10:30')
    ' s+ |' K4 W8 `4 t
    5 g3 d1 j- h- U) a; [. Q! xts
    7 g: \( Q5 k9 |+ F8 _Out[6]: Timestamp('2020-01-01 08:10:30')
    ( z, Q! g: M; w. U/ t% }1" O  h9 S, u  R% J  l
    2
    7 t; \( M+ Y2 {% p4 Y" O3
    ) v6 l$ p5 U3 A7 v49 N# `  ]0 ~# {; [* `  Q
    5
    8 |1 R/ |7 A# i/ Q9 O6! _- |3 X! k3 j  W% @' i9 H
    74 }# j4 z$ O4 c' h
    8) M& _2 ^; M- h9 E. T
    9" Y, ], h4 H/ ]4 V
    通过year, month, day, hour, min, second可以获取具体的数值:& y6 G  B$ b: W  L

    1 M: R: L) E0 f. lts.year
    0 t2 P1 w0 h6 L. L' f4 G$ C- k, Y6 wOut[7]: 2020
    $ u3 w% W' E( r4 `8 N, c  ~# U4 t1 F" P9 X
    ts.month
    + Z9 B. t% C6 y: YOut[8]: 1
    / s1 C& e5 I& f$ S/ L+ }# |2 U( P( D1 \9 T3 ^
    ts.day5 I3 C# Z. x+ @) M% E
    Out[9]: 1
    7 i0 V) s, l; H1 g, T, P4 W2 h; G$ p3 {$ w+ g- b$ Y7 w) X
    ts.hour: F5 f6 |. j5 j/ r% N0 Z# y
    Out[10]: 8
    & d. H  o' A' }+ L4 [+ Q9 W; P1 a* I# K% R
    ts.minute. F& Y- P. G& }3 J( l0 i
    Out[11]: 10
    ! z$ h, K' k; m+ w* H) K9 A  W& v$ k$ j0 J1 @: \% ?# u
    ts.second
    5 }* I4 U$ b, B" {6 e+ {6 TOut[12]: 30& k& {0 p1 B1 t
    % L: A* s3 L- }" S8 _6 U) i
    1# y/ F/ Z( g  r( y- O! U9 G
    2$ e6 c3 F6 L0 v
    3
    - C# c- m" t# |% _4
    , Q! m! q, S( f2 }7 j0 r5
    5 w; v- v5 j' S, A( f% {% m; C- b# q60 g3 F) l; _9 H5 `: c& j
    7
    5 ]* f$ `: ^! r: h4 y8
    ) b8 {% K5 t. U7 `99 i# t! @) T+ @5 o$ X: l
    102 [8 c+ J8 @0 A0 ]/ y3 t
    11" P( Y% y* A( v# Z
    12# q$ W/ h: W# P4 @& W
    13+ @6 _' w8 [+ ~1 d8 p9 w; a& u
    14
    . i9 G& Z$ G. f  p  A15& T; G' ^" }" q9 ^7 }
    16: X' Y/ `5 s9 _0 p  ~7 K
    17
    % G" }8 [& i, F7 z: C) B# 获取当前时间0 g3 x+ ]7 J2 g4 R: I" ?
    now=pd.Timestamp.now()
    0 D3 e0 M& _; `2 |! t( E9 P$ ]1* w& s% b8 X+ p2 N
    2
    ( P2 c  |! y0 }( ~% Z1 K在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    , e8 N& Z* d# R6 H5 h) k6 {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)
    % l( r& w/ u+ H" CTimeRange= & B( n! L) F+ w& w3 ]& s7 w( E  _. C
    10 + N2 P$ Z; _4 }; u- ?
    9
    + o3 u. B' {* V( y" Z ×60×60×24×365: j* _. Q" D* S1 _+ u
    2   c  M* Q; c) C" y
    64: o% x' ?3 J0 W. n& |+ S
    $ @  Y  P, P6 H0 {6 h6 K
    ​% h3 o' Q! s0 x; b
    ≈585(Years)) R. X( Z, h1 n  a

    ) ]/ g2 t( ^# K) C: p4 X5 N9 j通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    & [9 S/ G2 l; }# x, U
    8 `$ V* n0 u$ [& R& i2 C/ ]$ g: h; spd.Timestamp.max
    - j  e' F: u9 bOut[13]: Timestamp('2262-04-11 23:47:16.854775807')& {4 L- P6 y% Z3 g5 G9 [
    ; h5 G$ l" b, J0 [
    pd.Timestamp.min
    8 I2 Y& m" _9 i# XOut[14]: Timestamp('1677-09-21 00:12:43.145225')/ e: @/ T# G7 j/ \9 B$ b
    9 G+ J+ }, F/ @
    pd.Timestamp.max.year - pd.Timestamp.min.year0 Z& l# b" G% X4 {* a6 C" r
    Out[15]: 585+ @- C, c" f5 ~0 m: Y
    1) p( V3 I+ l5 v# Z& D, g& [- [
    2( J- w, t2 {5 a7 g
    3
    & C/ L- z( }1 y% |* Q6 V9 i1 i4* F! G2 ?& T1 d. n1 {7 t8 L6 U
    51 J4 X! Y0 Z0 p) L8 C
    6
    5 P5 Y# [# K, c3 _! T7+ t3 W# `6 k1 m8 [$ |0 u0 g+ w
    83 l: x6 D+ o2 O: ^1 D( Q
    10.2.2 Datetime序列的生成
    & Q6 P* Q, B6 M. s0 k' w) Tpandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,5 Q0 u  ~# b  z* b1 ?
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    + w( X" P1 Y9 |2 m. G1" f5 [' s: x$ b- n5 x3 o
    2
    : {; {- o) [- L9 r( Qpandas.to_datetime将arg转换为日期时间。. `- a- x% ^7 M/ v# o- P5 Z
    # n* X4 W1 m5 j# X, c; {
    arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。' o1 ?- H# M6 O- M' h
    errors:$ q  |6 q7 F- W5 B( |5 M) ]
    - ‘raise’:默认值,无效解析将引发异常' B1 @; F/ p/ ^; m1 G: Y
    - ‘raise’:无效解析将返回输入  R3 x6 b( _% A/ k  ^
    - ‘coerce’:无效解析将被设置为NaT, f0 [% `! A/ E4 z
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    , Z6 ^6 x2 @1 q- R! myearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    # F9 A# C7 p  F! X9 sutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
    7 ~% R" k9 k/ c1 _2 X0 Vformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    1 {4 A4 t3 r9 |  i5 eunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    % i# y7 Q' L2 `( _0 j5 ~# c8 ^to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    ' A7 M' x' @* {9 W5 m" p& ?pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])4 y3 f/ \9 @4 W6 p6 X1 _: E) l. d
    8 M: _3 x: }6 K* @( z, k
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)5 E. I% X3 X" A5 L8 y/ a
    1/ ^3 }1 O# T0 N2 ]
    2
    4 S7 Y% _5 s  ?0 S* g+ G3
    ; ?3 Q: z9 B) K, b在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    8 @# K. @$ H% P: j( A7 A$ }8 t. }, M6 i/ J
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    1 E8 d1 S1 q  H# z7 ]" O1 Ztemp
    3 \# j1 U6 k% T8 ~+ x" v* H
    , R$ x/ M0 L6 {+ r+ zDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    % P* ]$ l7 t( m5 G5 x6 W1: |0 J2 t, t/ Z& U2 u, B( D( M
    2: c" h- q- f" `+ F5 e0 ^
    3. f4 y4 q5 G# s( \1 v+ C4 g1 t8 T
    4
    2 c# ^( ?$ B  {5 H- d( J2 B  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    ( I+ [) @0 b( w1 {2 ~, f
    # ^, S7 U4 A& p) N) J$ h, R7 A9 \9 Gpd.Series(temp).head()
    / z4 T- \5 V' Z9 V2 X% v, M2 B2 K
    + `# y+ w/ e9 q1 T- a+ n0   2020-01-01, ]; \2 D1 u2 r% r3 x& |
    1   2020-01-039 g3 Q' o1 a# s$ V% F, t# I% |+ O
    dtype: datetime64[ns]' U0 i9 ?" N) _* w8 u
    1# a( i6 \' n( r- {, E" b- V
    2( Y' d0 N- X$ v5 C" b* h$ E5 n
    3- [0 v7 i" ]0 o* A
    4
    3 R+ J, _% L6 u; ^# o; q5
    9 N5 q; q9 y% K" f! \" Q$ G1 P下面的序列本身就是Series,所以不需要再转化。& S3 |. D+ {# ]4 D6 m8 {  J: _
    / \7 @, T4 e0 l2 ]
    df = pd.read_csv('../data/learn_pandas.csv')/ m$ n7 z! n+ v8 H# `3 S1 a0 Q6 N
    s = pd.to_datetime(df.Test_Date)' B# v0 P, n% q# y
    s.head()0 `: _6 i& C2 q2 f; z
    % `5 @) h: K& N4 H
    0   2019-10-05) y  N: i5 v9 \6 a9 C, p/ n6 ^
    1   2019-09-04
    - w; K' b: v9 e- C2   2019-09-129 g! K1 ]' W% S7 ~
    3   2020-01-03. A# h" N9 [3 Z; P
    4   2019-11-06
    4 L# F  r) P& Z1 S0 x: F& C) A! |: ZName: Test_Date, dtype: datetime64[ns]
    7 P+ ]* p: R, g, \: L* g7 ^9 a1# f0 j( I+ }& f  Y: [* R4 Q, l9 j" j
    2# J* }4 T  B) A; F# ~3 D2 @5 E
    3
    0 D! A/ x+ Z, @- {( a4 k! V' N4) {) g4 n7 O& B( O
    5' g& P, J3 {5 V0 O. N
    6' h  B, U7 P* D: z7 G& ]. o
    7# b: j' z( y$ s6 b3 \9 D
    8
    : G8 d* N% v* a- V9
    * S1 W3 |7 P) z9 N* y4 P2 `% B! B10
    5 e0 x9 P9 O* R+ c把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:; c7 x  k2 e; n, u4 \
    df_date_cols = pd.DataFrame({'year': [2020, 2020],
    5 y7 _9 p3 u. l8 k$ D: \+ V                             'month': [1, 1],
    . g1 k& X# P+ `  s* {. y                             'day': [1, 2],* o3 J* i6 Q: \1 ?9 A/ c3 e: E! ~
                                 'hour': [10, 20],; p* o  [' Z/ B) R+ r0 x
                                 'minute': [30, 50],
    + N. f/ g2 W7 R& C                             'second': [20, 40]})5 f! i1 t0 M0 G4 x$ Y; o* D
    pd.to_datetime(df_date_cols)4 y* H- w( |# f) P' _/ \" b! q: P4 Y

    $ m* G  C5 Q4 ~0 \& U7 \. Q0   2020-01-01 10:30:206 l+ q9 u0 U4 F5 u
    1   2020-01-02 20:50:40
    , W, F# e% l2 }7 _* _dtype: datetime64[ns]" w& Y, {1 B3 J4 \
    1' ~8 _' L0 ]4 A  n) R" ?
    2
    ! L) J0 [, M2 I1 {8 ?3* N* E2 ^! _' w3 C9 v) X4 B$ t4 x
    4
    6 z7 @3 x2 f7 k; l8 S0 G* H5
    0 R% [% J' B" A' |  H7 }63 u0 S1 a, ~1 H/ z7 E, Z
    7
    # X6 N( n: Q( L3 s4 _+ y* z8
    & Q; T* f/ G0 N* e. {91 o. z! N# M/ f; }4 c
    10" F( H5 j7 b% V
    11
    $ q) g# O0 u! V7 e- ydate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:& a8 H* D; \$ ?
    pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含' x5 m7 r/ R2 s% N2 @( W3 l$ R
    Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    ( A* P% @) C0 h1 r9 W* _: M! @
    2 k$ P+ l* a+ r# i* Q" Rpd.date_range('2020-1-1','2020-2-28', freq='10D')
    * L( ^6 ^4 J4 q2 Y7 t5 j0 m  w. uOut[26]:
    , ]+ U" n; h# e& l) TDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
    1 S; r* U5 p: N               '2020-02-10', '2020-02-20'],
    , W& r, h* C9 c0 i              dtype='datetime64[ns]', freq='10D')0 H, S0 e" I  J2 q' r

    % q5 q5 ~/ S$ F, j4 Cpd.date_range('2020-1-1',! k! ?3 K; q/ @" T$ l$ F8 [2 d* U
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
    ' W1 p3 f0 Q9 e2 }, K! e
    0 S' F( S, |( v" v, m( ?6 E0 T. SOut[27]: % O& X4 c( A4 L' P  ?: ?: i& T; [
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    ' n! V  b. X/ U( ?* c, G* K               '2020-01-24 04:48:00', '2020-02-04 19:12:00',
    . s) ]( ]; x( y; o6 N, E               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],$ O" Z7 V6 K! Y4 M7 x
                  dtype='datetime64[ns]', freq=None)
    * E% O3 y& g3 d) J* N7 u  p' P) e1 p0 }
    1+ ]2 {3 P  g$ B, u/ ~/ X1 y3 U: S
    2- g- L5 R) C8 l2 B& X3 F
    3
    3 m8 u8 u( ?4 z6 W0 q5 x4+ r6 E% F; Z6 |
    5* O# _8 @1 w6 T: U  S
    6
    4 C  G; Z! A1 P) n! Z4 H4 h7
    7 [% G  R4 \1 h3 H# P8
    4 |9 `7 o% B7 ~8 H9 b+ L" d9) Q2 \! a9 A2 P
    10
    ) n" A6 K. \3 c7 M11
    ) U& y5 H  [5 A6 W" q# s- |12
    3 S" }+ l; R9 C* E9 e+ a2 {' k9 s: @13
    ( i* q8 v% R8 g$ q% F$ j14) a2 o* S# ?* K
    150 r1 _# w# r1 x) `6 e- e9 V% @
    16
    / {4 @' U- t$ `! |$ g! m1 ^) \  S17
    3 x/ ]0 D- D6 g1 `1 q$ ]这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
    5 q; j! H2 s& Q% U3 r* h; @
    4 M1 p6 W+ D) {/ H4 u) u2 `【练一练】
    0 p2 v4 X6 h9 nTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。1 O4 c' x, m7 _+ w1 v2 m

    ! x' }6 @: n5 b0 E8 j. tls=['2020-01-01','2020-02-20']
    ( T8 K9 V6 {8 ~def dates(ls,n):  N* a) b- K0 A- [* }- P; u
        min=pd.Timestamp(ls[0]).value/10**9
    , Y+ Y. @9 E, \. L! S    max=pd.Timestamp(ls[1]).value/10**9& a- [/ R. O( r
        times=np.random.randint(min,max+1,n)
    8 l+ i2 @4 w( c6 F! S5 X, K: E    return  pd.to_datetime(times,unit='s')3 H# ~- u4 V: h4 b1 N
    dates(ls,10) * l* Q- z7 i- c; [) i

    # s' L0 X- N1 j4 b; zDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',: G0 p# U& w  e5 b% z
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    : E( ~* J* B1 y. c% B$ T  r1 Y: ]               '2020-02-15 00:18:33', '2020-02-11 02:18:07',
    . D+ _. ^" I; U- `& q( x& O               '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    , K/ b! y( X7 P# J# u7 _0 q               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    & w: n) y, y& ]. ]& |              dtype='datetime64[ns]', freq=None)
      J  G9 @  z1 U9 x( g4 w18 B6 s* I- {2 B
    2. C* {' _# ]9 ~; n! q/ Y
    3: F0 u" T# S* c9 Z! u5 k
    40 X7 e5 i) ]3 k* J) j. c
    5
    0 _: @3 }" W) d/ y$ m! s6 [, Q6# O3 M& K( d- ?3 J+ m5 u- K
    7
    ( k2 [# h9 m' d' R& ?8
    7 {5 R' x& V: Q2 y. ?! Q" {9
    1 J- W! ]) E3 K4 E3 _# N' X103 `5 s& O5 S! p8 {7 ~
    11# t# o5 n3 Z  U' P, ?- L7 A/ _
    12
    : n8 j7 c* G8 I1 w! y13
    * _7 s; A) H5 X5 w$ D. l6 a6 Y$ D14
    / W, G6 S5 q6 J: b+ fasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:" l8 }% \* f3 J; Q3 m9 t
    s = pd.Series(np.random.rand(5),
    & o8 M4 q; J) r% Z( f            index=pd.to_datetime([0 o) H* A% ^+ l" j+ W+ j  H
                    '2020-1-%d'%i for i in range(1,10,2)]))8 }* D% y6 j% J1 V0 h
    4 E; u5 c/ a- L# \2 c% ?  a( _0 M

    2 V7 {9 j) ~0 y$ o! j+ Cs.head(): K1 Y0 I" p8 R# l
    Out[29]:   g7 r6 u9 }/ A. d
    2020-01-01    0.836578+ @2 ?5 ]( P: F/ c
    2020-01-03    0.678419
    & r/ X5 _7 @/ ~0 y( l, F2020-01-05    0.711897
    $ J5 c  C  o; E. q% i5 e" U  ^2020-01-07    0.487429
    8 U" S* |4 ^/ {2020-01-09    0.604705
    , G* t; \) ~7 J6 O& g. f/ z5 Udtype: float64
    ( Y" V: X% n+ Y/ j  `' j+ H6 q
    " }4 ^" k- Z. vs.asfreq('D').head()- |, W: l. V2 r5 z% w! t
    Out[30]:
    - J$ c5 A: z$ X. o2 O  Y5 V6 s2020-01-01    0.836578  n* e7 Q2 \* W
    2020-01-02         NaN- ]$ D) ], u! ?- ^8 g( q
    2020-01-03    0.6784190 j# V$ Q9 }4 O2 E" A
    2020-01-04         NaN+ l- X# ?$ G) v4 o/ [' D
    2020-01-05    0.7118973 U3 y  ~9 \1 o# l
    Freq: D, dtype: float64+ w( p- ^3 A) |6 ?

    6 E% J( `. a' P# o" ts.asfreq('12H').head()5 s) v/ T+ k$ T" m( \' C! A+ ~
    Out[31]: # |! t) B( g" R8 G
    2020-01-01 00:00:00    0.836578$ T1 E+ X: `  ]7 A, t* Q
    2020-01-01 12:00:00         NaN
    8 X4 K* c. q! b: z& R% M: k% R2020-01-02 00:00:00         NaN
    4 M: O$ j, E$ Q! Q2020-01-02 12:00:00         NaN4 _1 `# c! A$ L
    2020-01-03 00:00:00    0.678419
    9 J! @. D/ [# h& `1 P) e4 UFreq: 12H, dtype: float640 D' f/ L# S# T& @3 M1 U6 p6 J
    5 `4 Q: L# U9 E
    1
    ( w) ^1 M3 n: m% Q7 @0 q! w2& @# D! H7 `7 i; a7 Q+ w5 k* M% R
    3
    4 A. G1 x# L- |4
    & F$ o! R4 X+ W% y+ i7 C5
      Z! [- M% Q  }6
    8 |& l: v: P; ~* b7
    , h1 Z% d8 l" r2 T+ k8) l3 n( O; k* M+ ^1 ?0 \7 Z
    95 {1 W# b7 I, V0 q  @- `: l. @$ _
    10, J' c& B% s0 c! K! v
    113 ^- B1 X# N: U# E( W2 ~
    12# H# Z" F$ [; n( k* P" m2 b- J, U
    131 U9 X, ~" n" n5 P1 ]2 B$ e
    140 V: Z/ D2 l3 d& ?8 y" C
    15! y& ~4 ~/ `, s8 w1 o; M
    16% s4 r7 j' K& X% {2 `! ~8 ~
    17
    " r" p6 _, u- [. O# W6 T18: w3 G' l3 W0 i8 C1 K  w& a, ]
    199 |3 j# d. F) [& ^
    209 R! }# c& i2 p
    217 H3 P: K4 m( D0 V4 k) w( m
    220 H( x: o& N0 ?5 Z, ?: {. M
    23) l4 s- ~/ L. d+ C# I) A) ?1 T. r/ h0 h
    244 p1 [0 u' G1 T2 G& p1 C
    251 P+ e, m* q2 p% [0 \9 g4 f
    260 i$ z# d$ m/ q9 z  j6 H& t
    27
    ! n, y) w. s5 k2 V28
    * t# \) P! n7 y29+ s" _# B9 N2 |* ~1 O4 R9 L7 b
    30
    9 u( [6 t( z; B4 u- U# j31
    & l( R9 ]& r7 J【NOTE】datetime64[ns] 序列的极值与均值, q. p0 `4 z/ c
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    / V( n, j& u' U: C: y! Z
    5 ?: Y* O) R4 w, h% M" V10.2.3 dt对象
    9 w+ i% n# R! U- g0 y' o3 Y  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    - o" [' _. x  z! _+ ?
    * i. |* P  q6 Q7 d7 V第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
    ( ?& K# D. |8 K- m0 Ls = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    . i) w2 k( i8 x3 s* [. z
    1 X, w6 o& R! W5 h0 B' p) \/ rs.dt.date
    2 @  ~) {$ k+ d- B# NOut[33]:
    ( s' u  U6 `+ O9 k8 \0    2020-01-01& Z7 z& x3 S& ?( F. w. g
    1    2020-01-02
    7 ^; t% n* e9 T8 Z+ f7 B2    2020-01-03* B# z- I9 q1 a) t/ K# K1 q
    dtype: object. p- h, O! Z$ ]. Q( P1 E7 `

    3 h4 V# u) T7 H$ Ns.dt.time# s; e' _9 D$ b" A( |% K+ [' l6 ^1 }
    Out[34]:
    # \; q+ ?) o# _: C. g% f0    00:00:00
    ' p9 t$ n' b; d$ A1    00:00:005 {& Y( p% ^- V- `7 K3 [* j
    2    00:00:00+ @; C- w7 H& V! i
    dtype: object! w; N- [. R0 a% n& ~; T
    4 _0 V( Y" C3 t- [4 ]
    s.dt.day
    * V) }" a# \" M1 O! m6 u8 y3 @Out[35]:
    . e$ ?  z& S" t" Z1 M0    1
    8 e: S/ G/ n5 A* D! N& }4 i, I1    2
    9 H$ n$ e7 z; P5 ~+ P2    3
    $ V) l, m+ m9 U5 Y9 C5 i+ k/ Adtype: int64
    4 h# u& I9 N* K3 G, y9 n/ j6 G$ q5 `5 }9 R0 R, p
    s.dt.daysinmonth
    $ [, c- n( e# H0 t2 c2 V' ^Out[36]: # o. E2 |2 i! g% H1 E3 E+ |
    0    31
    3 A! n% z% B3 W0 K9 a1    31
      S' O/ U# [: ^5 y2    31
    : `. s/ V- ^& x+ G9 Odtype: int649 r- E4 g( P! m" O, c
    " i4 E. M) p5 w
    17 q0 [# N4 X1 Y7 }( Y7 g8 c
    23 l0 N/ u6 M! q) z' K" S+ L" h2 y) H/ j
    3
    " R6 L$ q/ ?% R9 K* t5 i; H! C4, Y- m; f) c7 z1 v+ q' T
    50 D( C6 S& i4 v
    6
    / c( X( i9 P, ~, h1 M7  \! j  g2 F) ]5 F4 Y" b
    8
    1 |) s* }2 L1 Y2 z( U2 n8 ?7 Y9
    0 Q& Z% A, r0 d; u5 u- Z/ s3 Y4 Q10% K9 w  J) e9 o, q: \: o" }
    11
    - g1 p6 E. w8 _$ z12( h# M4 x8 V  o8 Y3 Y9 Y5 ^) J- K
    13
    1 F. ^  t" Q) V* K$ @) b149 u* G4 n' I: {0 l8 W) a/ K" h* t
    15
    . Y6 [4 ~+ ]: a0 L; M- O1 A165 i. E% T5 J9 g
    172 C- S% u3 S& J3 ^! F; U
    180 p" @- b/ V+ i/ h) E( \$ S& f4 r
    193 q6 {; B: f) G7 C( R
    20  j2 N' y) f( _/ }- S
    21
    ( }# P& ?, h# P, T6 o( F22
    " Y6 t' ~& F% l  M4 T8 u+ c1 S) H$ E0 E23
    4 {" U% ]( K2 Y+ p$ L240 c. O7 r& Y+ v$ F% ^" j& y
    25
    4 N. Z5 Z  |7 m260 _. G# ~  ~5 w4 }$ R
    27
    ' P/ D8 N; u) o8 t. o# b# [8 N$ A! X28
    " {0 q0 W2 _9 J29& I% v; g+ i! f+ l
      在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:4 g/ D8 Q2 m* k/ l4 Y

    3 o# `9 n( D& Y* K0 is.dt.dayofweek
      U0 f4 ~5 N/ ^' J4 L1 E; U* ]Out[37]: ) l+ Q# y( C' V3 t: f8 {8 C
    0    2* l. R/ n% x4 ]; _, B  T0 N) m
    1    34 q! B) `& z* B: y* x! o4 ^
    2    47 u' i% L# {' g0 r- Y1 @
    dtype: int644 q2 o( L' }/ A
    4 h0 j! e2 X8 B+ C, T( z: O+ n
    s.dt.month_name()8 V% D  f$ s& U* O7 l5 T
    Out[38]:
    7 K+ ~: w) B& ]$ F3 ]0    January8 w  ~( q0 {8 B& }+ `4 W! o* M
    1    January
    3 l% U6 Y& D2 I) s2    January
    5 F- }  Q, k( S$ Z( ~7 g4 Odtype: object
    7 `( l2 i; u2 `5 U+ `, Q  e9 W3 H7 M& z* i
    s.dt.day_name()
    2 B4 O7 p% N: N# b4 _& i' VOut[39]:
    1 i, T- n. a$ ]& U. b0    Wednesday4 L- r4 |# ]$ I1 b
    1     Thursday
    % H. s  P  P, w+ d6 v5 u. |& U2       Friday/ h4 Y1 n+ b; k! T/ e7 V
    dtype: object% a( }& e3 L6 R# ]7 m

    ' }! }3 ?7 f1 X' w, e+ k1# w% n- b) \, u8 T/ |
    23 y5 ], N& F6 t) P# r
    3
    9 h! l7 J3 U8 E. x4) A" p; |, U7 Q4 _5 Y2 l; H+ G- E
    5
    % K& z5 p- U, u) R# I) A6, w* `- ]! B6 ~
    7
    & H# e1 o; l2 }: A83 e) q0 D+ z9 Q6 v; c
    9/ m! |" Z+ o) I4 w5 B, m
    10  ^5 ^% [7 _# W, ?. B' ^
    11
    ! e% ~6 I# b7 S5 e+ g12
    ) W* q; h3 ^6 I9 N6 b! p+ g! B13% s* g) Z- V5 Q
    14. }7 R- s% e6 Q6 i
    156 F% M& f$ w1 N2 \, \% N
    16
    1 @4 Q' e9 m# F  D/ q' W2 |- y17
    % P3 H8 G4 D2 U/ h- r18! U& E6 U2 r: D9 N
    19
    9 E# J3 a% V; t* }20
    ( I1 H, }8 \+ f4 ^- [4 g/ J8 i+ l第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    4 u1 a8 b5 b( m% j8 `, is.dt.is_year_start # 还可选 is_quarter/month_start0 @. d7 k+ e1 Y
    Out[40]: ; y) c. s5 J# q0 ^" E# H( t% P/ r% H
    0     True" _, z) J; M9 m' U& Y) t& b2 M8 O
    1    False
    * ]! n! c& O- l. f8 S2    False9 A! Q0 B" ]3 U, G5 O$ f
    dtype: bool# A  k; I5 h- w- z3 a$ g
    4 Y9 S# p/ M, _# E6 u; U
    s.dt.is_year_end # 还可选 is_quarter/month_end3 K7 o+ I- c' g
    Out[41]:
    . D0 @. {" p) r2 B8 ^5 R* C7 Q0    False* g6 r* y/ u  I2 a1 U( r
    1    False2 @' u# R# L" G  x6 `- I7 i$ I
    2    False
      w4 J. `4 o& c0 w. A$ odtype: bool( z( ]  f/ t- f
    1  L/ i- c! h, s" k
    2/ c- u/ R6 P3 Y; {
    3
    3 h) k. F& z& o# C4$ b  L. x  H$ v) s3 s' r
    56 }" U5 s. {  y  {" g
    6
    + [/ X$ ~: p1 z7
    ' A5 O6 q. S$ Y: j! ?9 ?8
    + ^$ h% [- p! K8 x0 D9
    ( P" d- M! c' H# h10
    4 y; {% ?) S( @7 [$ s11
    + r* H* C& s1 a2 s3 @. o12
    7 i! q$ Q  r- z136 D1 l, b4 S% `: v! d. A
    第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。2 K% n( J2 |: I  H( `$ b+ P# L* g3 D. b+ S
    s = pd.Series(pd.date_range('2020-1-1 20:35:00',6 F) N3 M; T7 b- `7 j# e
                                '2020-1-1 22:35:00',
    2 o1 f. _( W2 N; ?4 f0 K                            freq='45min'))
    / x$ \- ^( B& K9 c1 t* ~. k7 Y" ^/ M8 i( I
    ; l  E: R# Z3 k7 `  A* L
    s
    8 u+ N! C" a/ O9 s0 fOut[43]:
    " I) A% {1 p3 g0   2020-01-01 20:35:00! ?2 w3 s0 i2 ?( \+ V+ `! ?
    1   2020-01-01 21:20:00+ h: e. z" m5 M' K% r- J
    2   2020-01-01 22:05:007 X- W6 a, }' v; U; n5 C
    dtype: datetime64[ns]2 |& w3 N) ~  o# M' V$ V; ]7 D

    . x" i1 G- m. N4 y; K) ]/ v: Bs.dt.round('1H')
    , V: G% E' K2 }1 DOut[44]: , G2 R) |& K0 I7 M5 J
    0   2020-01-01 21:00:00- u: E  B" a" ~  i) r# ]
    1   2020-01-01 21:00:00
    0 w& v% Q0 U9 z: _7 x* M2   2020-01-01 22:00:00
    6 h/ e: {, [6 rdtype: datetime64[ns]
    3 `; Z  R1 q5 }4 b% R1 v* V- U- v' Z/ Q
    s.dt.ceil('1H')
    9 L, `, ]+ ?) T$ b6 SOut[45]:
    4 c$ N6 U5 u4 ]+ Q' U/ U  U$ S0   2020-01-01 21:00:00
    $ P& G' \7 C. g6 m3 q7 f: f3 B1   2020-01-01 22:00:00
    7 d0 L6 x$ F  w1 Q2 ?! F9 s2   2020-01-01 23:00:008 d3 A$ a7 {8 f" y! j8 J
    dtype: datetime64[ns]8 H4 a/ a" K) I0 X; z* Z

    : I4 V, Z: p  P- G+ j% vs.dt.floor('1H')- @, ]7 M& H0 l& J2 A& c
    Out[46]:
    # L, }) s4 t$ p) A0   2020-01-01 20:00:006 e" Z# L, {5 ?0 l4 T6 r. l5 l
    1   2020-01-01 21:00:004 V& g: j9 Y. o$ e
    2   2020-01-01 22:00:00! R5 P* A! X- P2 `! s% j/ c
    dtype: datetime64[ns]# s5 M7 v7 j5 u5 r6 m5 m; j
    + }! _% A& w# V3 l7 k9 F: j9 l( {
    1
    ) j  U- M$ u( E% X' ]1 z2
    : Y1 k* A& c# S- |" C0 f+ v34 J: _3 f! Z$ j- v. c5 L
    45 g9 u' L% _- N7 c9 ]) b
    5' Y& P  M  d' G+ U: T
    6! Q+ y$ V  ]$ V% O% R! w
    71 l/ _0 D2 s$ i) C5 h
    8& Y6 f3 T( C/ Q: ^1 Q
    9$ v) m9 N  x( n) w' k4 h  {
    10
    , h0 B: \) `- S/ n+ q11# {0 ~; v( v  Y
    12- p) j" s* b7 }1 ^1 Z' s; P  H, K
    13
    7 M& X, V. ?% _144 }0 i$ h! @' ?" I& s2 t8 l+ U
    15! m( _" ]% i- @3 F  }1 s7 d
    167 F# c7 x# r+ @) D
    17
    4 O- b$ a3 F( e: O; e  n0 N4 y18
    ! z6 `* w8 f& J* }8 ?. }+ l' u195 W/ b5 _( p4 u5 B, Z
    20; l5 ~8 M# t$ ^6 N) S# n/ J7 E
    21
    % ?$ s4 y+ `  m( a$ e! ~; I  p223 E* V$ ^: q6 }5 E* G, o$ y
    23# k4 q/ h  B$ N" [7 r' b+ Q
    24
      u3 \, [$ \3 e) J25* B5 k8 u6 |" Q0 Y
    26
    ' }" p( g& T% k8 b( Q* d27" d; p) A" J+ y* y* s4 r
    287 @! l5 ^( _" ^
    29" n4 U' m) y8 i2 [$ V9 k4 @% o( z
    301 T2 c) H& A5 ~+ T& ]+ c2 i+ Y
    31, V$ \7 |, x6 \
    32
    " l: A# [2 \" O- i) ?; K( a6 J8 d10.2.4 时间戳的切片与索引8 z$ x2 I+ |/ F/ O
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
    6 @0 N. A2 R# s. F1 O) h8 T
    ) b7 u+ m+ D6 i$ m8 H# i利用dt对象和布尔条件联合使用
    3 [; p; L7 k0 p! u/ ?1 {利用切片,后者常用于连续时间戳。9 Y1 q" ^/ Q5 j9 y
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    ) m( f( F, Z  g2 W& Gidx = pd.Series(s.index).dt
    ) @) f& d3 F- F0 I2 N, Js.head()
    * _" f# V& F. H4 m9 P) |
      h$ C; W5 u9 Y* o2 \2020-01-01    0: d2 a  I: _0 j( v7 Z6 l% A1 Y
    2020-01-02    1
    + I% M+ I! P8 w0 o/ R5 T2020-01-03    12 p/ m3 I. `$ X" d" F; i
    2020-01-04    0
    + R9 P" k7 j2 `2020-01-05    0
    % p0 k2 A# Z# G' p) `" A+ YFreq: D, dtype: int325 r' c5 h3 }, H9 _& V
    1$ j" d' r- s/ B+ t5 {
    2
    ; `& M) C; _. _. q' Q- s' w  O4 y33 P% {: X& U* ~# R: ~( W
    4( Z) N3 V8 \; n
    5/ W4 ~$ U+ c1 I, C, u
    6
    6 R  k, P: ^7 f* j71 o4 p$ S! S; s4 `- k$ J3 M- u7 D: e
    8: R+ l  H* V1 h: [$ K
    9
    ( U! r, J& _# y0 w; y. H& T101 f& P% d' @# f& Z2 w
    Example1:每月的第一天或者最后一天+ v. k. y0 y( a  h

    8 L# g( I$ V+ u  {+ Is[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values4 K- r. `2 J& u" E* d1 Q, F
    Out[50]:
    % r' Q; `& Q5 j3 @$ W% }6 P2020-01-01    1
    7 O2 N5 {1 d, [0 u+ g2 A  i' i4 b9 R2020-01-31    0
    6 W- T# s$ D: ~( y3 u2020-02-01    13 }# n" o# ~& `1 m0 d3 D; y
    2020-02-29    1
    & g9 X# s% U, f9 T" c: e! s+ n2020-03-01    0
    + q: H0 x9 |2 y1 Y* p9 J1 {: Udtype: int32
    4 ^8 d2 V6 L! j1 |, \0 X1* N. D+ E. [. v) n9 X) d6 \- }
    2
    , C0 r- Y% g- i4 C3
    ( b" T' W, g$ y6 X5 \8 H4+ G3 F1 R# y+ d7 h
    55 o% L* S' n9 R: r
    6
    % B/ l: U6 D0 X5 o; U5 ^75 b  H9 D. n: v5 k# q% B
    8* q; e, f5 i. y' g0 A$ s' @+ e
    Example2:双休日) d  V' W, P) `8 O4 x& Q

    . z8 {4 d3 h" [) V! D1 us[idx.dayofweek.isin([5,6]).values].head()
    2 I: E+ k" a7 s% a! |Out[51]:
      c, P/ y8 C/ ^& c* M+ M7 ^2020-01-04    16 k% L3 Y2 m: O  M$ }2 o2 \
    2020-01-05    0
    ) Q- n: C/ S: ^% K2020-01-11    0% t- F1 `& ?6 m8 q& w1 Y
    2020-01-12    1. ?, L( L; v0 b$ n$ j, I0 ]
    2020-01-18    1
    2 [- ]5 q; B& i$ J( q0 D, y4 u5 xdtype: int32. ]5 R7 M9 g2 o9 ^$ W
    18 l7 Z# M  O& g% P/ f
    28 w1 U) E5 ~; S
    3
    3 u6 I' x; J) b. U4
      Y' N8 V+ ^7 Q$ @9 h2 E5
    ! g5 F+ V: P6 F- R( g6
    - J' @) @0 P/ |: A7
    0 a# L: @% M8 r9 c9 I; f. U) j8, x4 ~7 }8 y. @6 d6 U0 K2 S  q" K( G
    Example3:取出单日值# @2 H. `/ o0 R  C1 g

    8 X- a  w) x  g- x+ B, o  s( P! us['2020-01-01']/ o, C# r4 e- _( o' ?7 [! ?
    Out[52]: 1
      m+ T4 t# k; L$ V1 Z  ?  C- V7 C0 v) \# r( R
    s['20200101'] # 自动转换标准格式& D1 [" K6 [* r  z9 e
    Out[53]: 1
    ' u: p5 a6 P/ a0 ]8 x" X1
    7 i7 T: k# H( q  n, f) A, q2
    $ Q; N% n3 ~, N  g$ O( F: w3* l3 F& E0 l5 K
    4( m: e' q/ `; W( c; L+ R
    5' L, N+ K. @  K" ^1 x" \4 p
    Example4:取出七月5 Q7 A+ A# e4 F0 D

    9 ~5 \( W& I! J" B' rs['2020-07'].head()3 j: j9 j' E; {0 Y0 j$ H
    Out[54]: . j$ W) |" m- x. }! F- }6 _
    2020-07-01    0
    / e" E7 Z) \& B( r% `# J) f2020-07-02    1
    & j) e. E- C4 w% V& R1 Z2020-07-03    0/ W' C) ]; A) u7 R4 ^8 s( C
    2020-07-04    0
    ) ]3 ?3 x0 C4 ^# a# L! r9 ]2020-07-05    0' I# F- n+ X" v% r3 P& [" p/ p' E7 \
    Freq: D, dtype: int32# q9 S, T6 J9 r% }' W
    1$ Z, `5 E/ o/ L5 C
    2, M& a: \1 V2 f* `& }8 v) n$ P
    3
    , A: u5 ?- M0 S9 x- o46 [$ j, U  l3 o% R" M! L
    5
    & U, q& X  a4 F; l61 W- L/ K; J) p9 D6 H5 \! g
    78 x7 P- c1 z) C- s
    8
    " n# o- M! @! JExample5:取出5月初至7月15日
    5 O, `) A( d- D( ~+ L- `* A: @) f4 C' j% f6 p3 {
    s['2020-05':'2020-7-15'].head()
    / h/ o/ q& Y" c8 L. }Out[55]:
    2 d: j3 @. |8 t+ Z6 g2020-05-01    0. x4 i+ }5 k9 _& Q3 D
    2020-05-02    1) i& O& N* q' d6 H
    2020-05-03    0
    : j( `3 L( t* }. R9 q- i/ l0 M2020-05-04    1
    ) U" }, x  K: x$ L8 z3 l' E9 X- K2020-05-05    1$ M* G3 ]* e8 j0 n0 l
    Freq: D, dtype: int32$ r' t$ x& e1 t7 z: g/ [
    8 O( o9 K  o: Q6 Y2 N% \3 ]
    s['2020-05':'2020-7-15'].tail()
    3 d' c; p) I& R, ^9 C) |( b" X: t2 EOut[56]:   p& U( n: d3 S, Q  X
    2020-07-11    0  g, O0 ~; J1 F$ E
    2020-07-12    08 A  N7 J$ f# E& C
    2020-07-13    1
    ' y! P1 b$ l0 m1 Q) J! b( y2020-07-14    0
    / H7 V0 p" r5 t4 E; @4 Y2020-07-15    1
    8 a8 U. Q3 s! K( F& R. {3 S4 t# RFreq: D, dtype: int32+ q- u# i7 C' \1 J) t: [* D# U& _
    8 i8 \1 Y7 u5 C3 z- y( b' B. y
    1
    - Y2 d4 i) ^2 e0 X) r0 ?23 A: i2 h/ K8 ], N1 ~
    38 g& r% R  v$ X
    4
      n. E3 w3 V' ]7 i. z5 t5. H" u/ x# x# j; i
    6
    8 T, I" H6 }* D+ {- A$ c2 V79 m0 m0 ~1 P, o7 t$ W
    8' q3 y! B* d, _; t$ m- c
    9
    6 I9 p! h" u$ p# I4 W8 W# n9 f: J10* `" i% z- P' C- j* n
    11
    ( N! ]" ~) d8 [* q# a4 i" ^/ g12
    4 s5 t" M7 x5 f3 q; e135 y( K1 a. a, Q2 f* |
    14
    9 g& Q( o  k  I* s7 |$ f151 k0 s, W% M3 n
    16
    : Y. H3 Q  U; v6 B; a* M4 @6 Q/ a. v17& g3 C9 b) W2 U/ Q) Q& f; {% K
    10.3 时间差9 G# R) A4 Q# @- S2 L7 |
    10.3.1 Timedelta的生成* N) c/ S+ o! A  [! N; b
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)
    1 R4 L+ ?- o# e4 e( w  n/ @  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。( t) O/ ?1 ^! A+ O3 p- O" d1 i* S
      可能的值有:) c' H  u8 B% c9 k. c( p

    8 h1 s- b% U1 `3 i5 \2 v9 O‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
      x4 W$ ?& K- Y: r‘days’ or ‘day’
    - C3 u% e8 r" v. M& p5 m3 @‘hours’, ‘hour’, ‘hr’, or ‘h’9 v. @- T: f2 \
    ‘minutes’, ‘minute’, ‘min’, or ‘m’
    ) C: p7 A9 F) u‘seconds’, ‘second’, or ‘sec’
    : z; m$ e7 {" l- ^& S1 F( d1 c4 ]毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    8 T1 M5 O- @+ V5 n微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    5 K% B. M2 W6 C, g: q+ l: I2 v纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    0 h% H4 s4 u& ], }- e+ y: ]时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:: l+ ^+ b) y6 `* I7 P/ B
    pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')& L& t0 V% M  s! T; s- Z
    Out[57]: Timedelta('1 days 00:25:00')
    5 J; v9 @) Q7 |/ {1 l( Q" ^) A. _  Z! |( m2 ~4 {( B  f# h7 P- S
    pd.Timedelta(days=1, minutes=25) # 需要注意加s+ T& W/ I7 U+ @( O* }
    Out[58]: Timedelta('1 days 00:25:00')' p$ o9 f- i3 K! [$ [' t
    ( P: I8 t/ Q9 {$ ?9 Z( o7 e8 i) X
    pd.Timedelta('1 days 25 minutes') # 字符串生成
    0 K: _7 N8 _7 w: C& O( mOut[59]: Timedelta('1 days 00:25:00')
    / }  I) T% V! @# q
    . ~! @7 g/ T; @0 G" v' npd.Timedelta(1, "d"): U! l8 |' s3 B0 f  v) D, J
    Out[58]: Timedelta('1 days 00:00:00')* r3 M6 w- L, a! B; p, F( Q  V
    1
    ! V) b3 _/ a3 T$ V/ P# I" J$ {2
    9 a1 ?6 \( x* V7 V4 a3
    * ^. z+ c7 R7 S6 i4& X  O" ]! x8 V( E- V/ X# @
    54 s( p4 ^$ l# E) d$ |
    6
    2 Y0 u6 [, t0 S1 Y0 I  Q72 E5 ]/ I* A* g6 I- A
    8
    ! W+ L; g6 _1 _% E  K' T9) ~9 a' }7 t0 Q" v* ^- m, C% d
    10
    6 h. n# r% u/ \' `! A, p1 m11
    ) w7 q( |2 v) \( K生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :7 R8 {! v" c% j7 l' B1 A
    s = pd.to_timedelta(df.Time_Record)  o) r/ ~6 a6 V

    4 }% E3 K3 @/ ks.head()% G% R* Y, T; Q6 r
    Out[61]: + B. j  P5 j5 N' {4 e8 k
    0   0 days 00:04:34
    ( C4 f: C9 S2 z* A4 z1   0 days 00:04:20
    - X" e1 Y# L. ^8 w2   0 days 00:05:22
    ! t5 B% R' H- y+ _; c9 V4 O& j4 h, H2 D3   0 days 00:04:08/ B. p- U6 H2 m( y" {* i8 Z
    4   0 days 00:05:22
    ( f! V) \' P/ A3 F5 UName: Time_Record, dtype: timedelta64[ns]
    0 l: X  R7 _2 w. G( L! _- P1
    ( L6 D4 D0 y) V  ]- s5 ?$ m  V2) P* k2 @/ I; j1 U
    3
    & H9 v+ E9 @% I! i$ |' R- {4% s( e/ s: h9 v8 Z
    53 ~+ z6 f. f! V( H: V8 P1 D! R/ s
    6
    , o/ j6 m  Y6 Z9 _$ o3 J78 l8 R1 K' T2 ?& g$ I& I1 k& z
    8- u2 }1 a- M7 t8 }2 q$ [
    99 Y2 Z: m: Q$ x+ G: L- o$ V  N
    10. H/ }* y8 U- }- `- m' W
    与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:/ l2 A8 \! v1 g. v* l1 D, D! _6 I
    pd.timedelta_range('0s', '1000s', freq='6min')
    1 d6 a5 v( E# `8 u, iOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')8 h$ ?1 L; n9 x; X/ a$ X% h

    4 m" f# x: v, `- A, e& C% l2 bpd.timedelta_range('0s', '1000s', periods=3)$ Z. e  {/ x: M6 x, b* }
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    % o" b2 Z+ t# z- N) R1+ C& }- i; _: |) B
    2. {1 c, W0 k! V7 U7 D! `# D& j
    3
    $ _* n- Y6 f4 e2 u7 e/ S1 z6 H8 |9 O44 q! x1 d1 H$ n( G8 d, ?9 j
    5
    : B& H8 K9 z! [) n* o! m9 ]$ J; H  u. a对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    5 h* r( d) |8 a; U3 Gs.dt.seconds.head()
    , o  i2 i; @' J; o8 OOut[64]:
    $ [6 r$ g2 M* {! P, x& g: U0    274/ Y, v: {6 \* v
    1    260
    " K" T7 T+ S# }$ G% n- i2    322# T  c: ?0 g* f7 @$ w9 f1 e- d
    3    248+ M6 x4 @  w% ^/ n4 Z6 L4 w4 i
    4    322
    ! X4 J  F# ~( e) @$ _; iName: Time_Record, dtype: int64- p# B% U1 y" t$ i9 ?& A
    17 I- r9 n; l  S4 r' X
    2
    ! T/ e' o! N& `& v! A3. a, P% x  y5 B" x# m
    4+ j. |% t, f( @
    5
    , q3 [, {6 e7 H% h7 c5 S6% P  Y; f" T) m4 D5 z$ K( }
    7
    * r* r# H: D) F; V( z8
    , x! w8 c. x5 O如果不想对天数取余而直接对应秒数,可以使用total_seconds) j* G5 ], F9 Q; u2 S+ ?( a

    $ O, p$ U( W+ p9 B; es.dt.total_seconds().head()
    ) a8 a; N; s- P4 [1 @3 }8 `9 q( WOut[65]: ; J" @2 ~4 q% i' a. ^; J
    0    274.0
    + ]# J/ e/ T: p# O1    260.0
    0 r8 R: ?2 @, f# F, U2    322.0- v- k9 O! @6 }( I. K% C' T
    3    248.0
    1 ]8 x& S5 N3 [2 P) z+ t5 z6 N4    322.06 Y! _+ ]. n8 _/ D
    Name: Time_Record, dtype: float64
    + E6 L5 L  R2 @$ L6 T12 a( e8 i. x, d4 }
    23 @3 {4 m( j) y  d! i% s# f( i0 }! i
    38 _0 H4 S0 ^* n% L' `2 r0 y
    4) b+ E% @0 C5 t7 a8 E4 D" o
    5
    : U5 g' D4 Q  a$ B) k( ^9 @6
    " b8 b# G0 B8 {, P- ?3 d1 {7* L5 _( O6 q# W7 Y# u7 a
    8
      r" @, o: B. F$ z0 O$ c& {与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    5 @7 b! y5 G* w4 y/ Y4 W5 Q
    ! y7 o2 z+ C5 s( Z: j. _! G$ npd.to_timedelta(df.Time_Record).dt.round('min').head()
    1 U, I0 r" G) S: S9 a) T- gOut[66]:
      A& R$ z' o( j0   0 days 00:05:00
    , z2 E1 n9 f% \) P9 q5 S8 r3 c- O1   0 days 00:04:00, ]: X; i9 F8 a1 W& p8 ]; @  `
    2   0 days 00:05:00. H" J. H5 f; E  h3 D( D
    3   0 days 00:04:00
    9 r! H4 }% U) i4 u5 L; Q4   0 days 00:05:001 f6 E- z9 b2 u7 ?, h
    Name: Time_Record, dtype: timedelta64[ns]8 \" c. ^6 d$ n* ]; N, K8 s. O1 f
    13 p# j, f  x! L# T
    2
    : S! G* ?. h4 c; g3
    9 l# T1 n$ M* G; L. B4
    + n( G! C$ D/ v* F* w; s6 F5- b- K; _' W2 I. t# a" s* L
    6
      B: ^5 E8 _* Z" T1 J. i7! j: D5 ^; T9 z  o1 P) `
    88 X1 O/ F+ [- \! o3 c
    10.2.2 Timedelta的运算
    & s5 n3 `7 Z: F, e( y单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:2 y, A& F, L. b7 p
    td1 = pd.Timedelta(days=1), [5 `8 h8 A5 r9 |1 [$ i2 l2 S
    td2 = pd.Timedelta(days=3)
    9 E7 A9 H9 P7 M, d5 T5 Lts = pd.Timestamp('20200101')( r3 }/ ~. _# \7 E2 Z  m2 Z' Y
    5 ]4 x/ Z" K/ K# n9 \4 y* N8 R
    td1 * 2! a' t$ H/ c0 O  J0 j
    Out[70]: Timedelta('2 days 00:00:00')/ \. L- @' C$ Z/ w$ B& D, Z; O
    5 D+ [* d1 Z8 _- [( f
    td2 - td1
    2 G  O1 K5 V# A9 z+ z! E9 a% O2 vOut[71]: Timedelta('2 days 00:00:00'); x: F: }, c/ f' B

    % Z' Q/ C. L' ]4 Uts + td1
    5 Y1 X* X& e, R+ h* E  vOut[72]: Timestamp('2020-01-02 00:00:00')" i1 C8 L, }" U9 w
    / _5 }) f& x& z9 K9 ^" |% y
    ts - td1
    / |9 _  b6 s! R4 s* O% w/ s4 O- E2 jOut[73]: Timestamp('2019-12-31 00:00:00')! w+ @. {6 m% Q" A$ \6 _' K
    11 U4 L4 W6 [% Z0 U2 Z
    2
    : d( k. w4 O# a6 z5 u3
    : r4 E  I% v4 F5 E4
    8 O! J  Q" Y! p; H50 p6 v. h! ], M5 k
    6
    ' `* L* m- w3 a+ J& E! x/ e" [% s* w73 j9 C  D4 W% M
    8$ Y. J# _- n& \: v* B/ h
    9
    * \. d6 z0 K" E8 T8 _5 ]10. }# R, a% D/ w" R$ _
    11
    7 g" P: b& B% x$ M12) Q  ~' F# r( `2 v8 h
    13% I+ i' t2 i( u: X' u) T
    14
    + l6 e8 N( W/ P15
    0 {. d2 L( a( N3 _8 N时间差的序列的运算,和上面方法相同:
    . ]4 \. W6 Z& }- v; h: {* @td1 = pd.timedelta_range(start='1 days', periods=5)
    ' C9 R0 |* k4 b- Y, j0 A' Ytd2 = pd.timedelta_range(start='12 hours'," v; k5 y& J. n$ c8 \! B3 _7 e
                             freq='2H',+ D0 M" [/ ]( N0 }
                             periods=5)
    ! l# v" I3 l3 Q8 E: U! mts = pd.date_range('20200101', '20200105')' C6 k! f+ n# a( F
    td1,td2,ts
    ! D  N7 `$ N! V
    . L% o) W, s! q2 Q8 r& xTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    + _! w% M7 m1 M- U- M, o" t# _9 sTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    , h% K9 K) B* a; z7 H; ^                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')) d# X; D, r0 B  `* j
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',- p- _6 S" U9 e
                   '2020-01-05'],
    + T* C1 V  d2 f              dtype='datetime64[ns]', freq='D')
    0 l2 O& a( W" g1
    # n# D( j! s% z6 f+ i23 i$ G9 W/ y& U+ P" N
    3
    6 E0 O& a# |, j. n( ~' t4' J5 U, b* I0 e- _% w1 e
    5# w( h0 R* d; [* a
    6; n/ X! {; B" Y
    7
    8 Z8 W  z; \) z+ W# O8 k8
    5 x2 W7 m' C. J, g% n/ ^( F9
    ' Z. `# |) W4 ~1 @( I108 }/ V* k, Z8 u
    11. n- W0 E9 o* V  T* r8 _7 B
    12) l/ u; x2 Z- G& \! f+ w
    13
    4 _9 C; c5 ~2 Rtd1 * 5; ~  f. }9 a" R' |
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    ; o: g/ j9 Y+ J5 p! l1 f0 Q8 ]0 x: Q) Y# c3 G- H* d1 F( v) }! H3 ]
    td1 * pd.Series(list(range(5))) # 逐个相乘" c! L3 C0 D. O8 _
    Out[78]:
    / i/ G5 I7 r6 M* T! ?0    0 days
    $ ^: ^  g* j3 c2 Y7 N8 z5 ^" v: L5 p  e+ q1    2 days& [9 R, z" F: ~; G
    2    6 days
    7 [- E( ^* x2 M* ~) }3   12 days
    # ]) t4 h5 b' H; l, |0 M4   20 days
    ! |3 W% n; l! c# @6 Rdtype: timedelta64[ns]
    / n2 ^' Q) d4 U3 r) A2 T- @
    ( U) w- L; K6 f6 Ctd1 - td2; A2 R: r' s, \3 `( f7 v
    Out[79]: 4 |. \+ R" D" C8 ]$ U
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',: j( r" @1 ^/ I
                    '3 days 06:00:00', '4 days 04:00:00'],% ?: _, ]6 c) I6 C' [- t4 D
                   dtype='timedelta64[ns]', freq=None)
    5 t& i  F3 Y( @1 Q+ W' H. p- x% ?5 L8 m- Y) y) \1 G6 W% ]5 E
    td1 + pd.Timestamp('20200101')
    3 t/ O/ K% n0 Y0 {Out[80]:
    % U' k% a6 Q; h2 FDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',0 I5 k4 N2 B) [
                   '2020-01-06'],dtype='datetime64[ns]', freq='D')
    ( s2 h( n  n3 m- Z3 S- j6 E6 y2 Z
    0 l4 O( k2 k" y4 _td1 + ts # 逐个相加5 G' l) d; Z  H5 @3 g
    Out[81]: . S( }6 z( m5 ]: V7 T8 b
    DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',) ^0 q9 l" q; P+ ~' K( w& L; q) }% ~
                   '2020-01-10'],
    . Z0 e: A( c) [              dtype='datetime64[ns]', freq=None): t- ^& v; T* k, t: C

    - Q" L8 |& P( V- T5 }1# q: G- N- ]  W' O6 v/ B
    2
    8 D# ~- Q3 `4 f% s0 R" ~4 j3
    0 [! J+ K/ ^5 X1 v, i$ i" `% H4
    5 z) z& {6 ~1 H; b3 U+ ~57 S: V, k) J! `: g) ~" d
    62 @8 l: M! l' P
    7- u* m0 \, g4 s& f) N3 i
    8+ o8 R' H$ i  L" L+ s6 T% j/ ~* h. A
    9& ?% D$ C: W1 n- Z1 z2 G
    10
    ) l: F! N. Z" k# c1 J2 y* G0 ^11  \/ c$ S& y/ h' v
    12& d. L( u" U# e/ }" g- z5 c" O+ X
    139 \; I1 K' o9 {1 Q* Q
    14
    ' ]3 U$ N$ L8 e8 G  P1 U150 S" B, N. [* c/ w
    16
    ; v3 Y" [" e& G  M* X# Y175 a8 l5 @' s' O& c4 a/ L
    189 p& V$ _# Z& |1 g
    19: a& Z) X1 O9 x+ ]  L5 g: m* ~
    20
    & K; V. n" ?4 h( k( Y' c4 `) K215 R- i% j% l7 r5 \. @
    22
    " x& n0 }- z/ m/ d233 G; Y- W( \4 v) k1 I
    24
    ( i4 S- P" Z! U# s. B8 o4 ?259 a: E. V  [' O: B8 T
    26$ y4 l+ h7 s' _: J* O9 H
    27
    & r$ g1 Q( J+ P6 G286 Q+ c1 ~: i/ t; C, ]( U
    10.4 日期偏置5 Z+ v* ?0 H) S
    10.4.1 Offset对象9 `. J' k% ^( c; [5 E( {
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    8 B8 t. d% B$ ?3 r# f' p" j' y% Z4 i
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:$ d9 f# \5 Q3 u" s
    4 }9 C7 R, E4 L
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    . j- S* L; w$ p5 @" \$ d( |s.kwds:{‘week’: 0, ‘weekday’: 0}
    1 p3 d9 }) W- E$ [8 a( C1 e$ |s.wek/s.weekday:顾名思义
    " S5 O) V  s: b7 g5 J# B( x* U有14个方法,包括:
    # [% y6 o/ R; G4 K6 t6 c5 l5 X  u2 T' U; H3 A9 J
    DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    ! L2 m) L, U+ X" W5 _$ opandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
    8 [# m! m, J: ~( e9 Y% C
    4 A$ O3 }% W% G9 q" G+ O9 W7 f; O有两个参数:, @/ d4 s. o4 F8 V! N5 S7 v
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。- s9 Z3 ]5 A: o7 }+ r# A
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)! `, K0 M2 k- q& E1 ]9 Q9 S
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。* N) M( M4 E5 l! r

    $ J1 {* _1 I# _4 @+ t9 qpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)% Q: }! n) ]1 l* K' b
    Out[82]: Timestamp('2020-09-07 00:00:00')* v1 D0 s4 ?  ^+ }0 {& P! y7 f
    , i, q& S: [1 ?' {0 J) K4 S/ \
    pd.Timestamp('20200907') + pd.offsets.BDay(30)% D5 O+ K) R1 w- P0 P
    Out[83]: Timestamp('2020-10-19 00:00:00')# ?2 f' |: v' `
    1% K$ ^2 N0 [. c0 }, I. B/ p( [
    2; M% w9 i8 I* i# ~% J# G/ R
    3
    : ?7 ^: V4 i& O1 {4
    9 F( h5 |7 I/ V1 N/ `/ D. A7 b5$ `7 M+ t7 F1 D5 s
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
    ( A  M+ ]7 V  j
    4 J4 |0 L5 [  ?0 a. F) y" Zpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)) q! J9 @& k: [" J, m2 y
    Out[84]: Timestamp('2020-08-03 00:00:00')
    * |7 K, y- J: ~6 `) v, E# c% |# Q# Z
    pd.Timestamp('20200907') - pd.offsets.BDay(30)) a5 [$ z# X" `( T4 y
    Out[85]: Timestamp('2020-07-27 00:00:00')1 i# J: C# W( a# o: g' l
    8 `" j! G$ E- s7 o# l
    pd.Timestamp('20200907') + pd.offsets.MonthEnd()+ J+ o; r3 X. {4 r3 i+ ~
    Out[86]: Timestamp('2020-09-30 00:00:00'). n. q5 }. \4 [; @) p
    1
    9 }" H6 x0 U, N2
    - Z) ]0 B  d: C! G, R* H3  m3 L2 Q8 E! h9 c* L! J, @7 R
    4- e6 L/ s0 u* k# B- T" M
    5
    4 R  J" g1 h1 a6$ i, x2 h$ p0 n, o9 B
    7
    1 D! [% x" z6 ~. G/ [# r+ N: J5 S9 M8
    - e+ u! h8 w; F% w+ g6 s. ], ~& l  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    * g# t# A- a& a" K6 `  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:+ s6 h5 p) V0 t3 A

    / Z! R5 U% h3 w+ gmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    / p6 D1 [$ x2 _( K% r! q, b: d  B" cdr = pd.date_range('20200108', '20200111')( Y$ n' C6 J* b7 i( ^

    0 \3 p7 }) b! S5 V' L/ _# Ndr.to_series().dt.dayofweek- x2 A4 q4 m, k! X$ A( c, _2 l
    Out[89]:
    % X( b& U3 h- Y4 q5 ?; Y' G2020-01-08    25 q) q+ M% p  L( O
    2020-01-09    36 ]6 A) E$ I4 K% F7 K% \0 q# ]
    2020-01-10    4
    & S  R) I' Q; E' X7 ^2020-01-11    53 u  {4 a$ X3 s, x5 L6 W
    Freq: D, dtype: int64
    ; m# N0 p; O7 W2 W! F4 o1 a( F
    - K7 }: p* V' k+ L6 A5 a# F$ j1 ~[i + my_filter for i in dr]
    ! C# N: J% F+ j0 i+ K8 GOut[90]: - A/ X6 v/ o- Y0 T; t! Z; X% n
    [Timestamp('2020-01-10 00:00:00'),- i; f: i2 [( G7 ]: S3 j
    Timestamp('2020-01-10 00:00:00'),
    ) }/ s2 k; I7 ^9 w# _ Timestamp('2020-01-15 00:00:00'),
    1 p$ ~; X0 g& B  `) S. t Timestamp('2020-01-15 00:00:00')]
    7 V, g: v/ ~+ a: i8 z. X
    ; h! `* l% Q5 X4 Q# T+ X% i10 ~1 h1 H' a3 c2 R* g0 D; b
    2
    $ H# o+ }8 Y! c1 }! A3
    - j5 E& T( n. V, m$ y49 v: A  o( R# t! v
    5
    " Z1 `! B" {- L( W4 I( F3 n# p6
    3 y5 p1 J. H6 a/ L6 T7! |& e4 B7 }) A0 x# U
    8( T3 U5 A  V5 N9 l. Y
    9
    ; S' i6 n. b' g7 r3 W. R$ X10
    ( A7 `! B; X3 R4 f/ s2 Y% s/ t110 O# U# G# R3 H% t* M4 W$ \
    12" Z) n  P- G- G
    139 k& ^, m1 T4 b3 P
    14
    : O( T& ~. O# ~  i- T15
    2 `6 N4 O2 r% K/ Q4 r. Z16: ^$ ^0 x% c" r3 q& C
    17
    6 ~' x: Z$ [/ y$ R  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
    0 H6 J$ [6 d9 v; z, d2 _! b% g* X* {& g3 s
    【CAUTION】不要使用部分Offset8 z+ D+ }/ a" }* x
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。# ~9 ?" ?6 w# W" D( X) T1 ]
    % C* q0 m/ C- f1 P7 y) w& k
    10.4.2 偏置字符串
    1 q% C1 x2 x; e; ~; I, [0 l: O& d1 u  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
    + ^! v0 r) E/ |0 D% D
    ( ^- ~! P  [" ~. `2 U  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    * S, x! \2 U- y! [2 Y1 v, ?# z1 b, A( j/ ^4 ?& ^
    pd.date_range('20200101','20200331', freq='MS') # 月初
      T6 R* Y) c7 ?; ]- h+ AOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')2 @7 {3 M4 ]" \* q2 r% L+ H- k& k

    . g: N9 N% y6 L2 p7 ~9 c0 \- ypd.date_range('20200101','20200331', freq='M') # 月末% ]9 s/ ?7 I2 p5 R0 P) D, g
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    . K( w$ D6 X# U0 Z! O; R* I' y, W' @' d4 r, v6 Q; S
    pd.date_range('20200101','20200110', freq='B') # 工作日
    6 e6 p5 {: g$ k  Z. f! bOut[93]:
    $ y7 P. @* V/ S/ V, Q5 r1 s/ H7 @DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    , ^' X! y  z; G8 g5 s9 f0 l               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    0 F3 b% x+ `# m  K8 U" {$ x              dtype='datetime64[ns]', freq='B')
    1 @& H8 N0 U2 ~# c, d9 \+ O$ C* p0 {/ p0 ^& P3 j
    pd.date_range('20200101','20200201', freq='W-MON') # 周一
    1 V# L, T! P/ A' |Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    1 z  v1 t& Q6 l- c( [# i7 R4 W* w
    : b3 Y, P9 y% ^% o0 x: o: Rpd.date_range('20200101','20200201',: E8 p5 L8 T6 l+ Z! |1 x. N- d
                  freq='WOM-1MON') # 每月第一个周一1 B' V4 N& b) Y/ d

    1 T" `7 M! B! X% m3 e) GOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'), }" I+ x% U9 s, b2 X

    / ^( L- @. N: |$ K9 N# T3 |/ z1" s5 l# v  w' x. j7 k! ~
    20 ^8 J( J" p) Y+ v! b4 _% x
    3
    . s9 ~1 `- I7 `& t" h8 B0 [49 Y( `, Y+ E5 Q
    5
    + J9 t& ]' P1 p- u2 Z" O% o/ R6
    & U8 l5 H; {7 T$ q/ w7' c$ B2 G6 P- k/ v3 n0 D
    81 U" A1 a1 Y# I$ @0 g" h. C
    9
    5 o( i- P' }: N3 |105 c* ]0 H8 J5 Y1 X% Q% A
    11
    & F, `, |/ l: ?3 p& p7 e- L2 H12: F; z5 X4 `8 p# T3 z* F
    13
    - o/ g8 c" o  K14
    5 V2 g  Q, h- H# L15) `! ~3 u& G1 \! T2 }6 G- g* ]
    16
    : y) V# X+ ?9 k) N3 E$ c* n9 s171 H/ ~- c' C' E. h7 E9 G4 ?
    18" ]; C! m2 S) @6 H( u
    199 E5 G' N) G1 P' l. W
    上面的这些字符串,等价于使用如下的 Offset 对象:
    & n- b# \4 y4 n) g
    ( o  ]% d- P! _: [9 Rpd.date_range('20200101','20200331',! R8 a( b3 x9 K9 R. w+ T
                  freq=pd.offsets.MonthBegin())
    + [1 [7 U( Y) \. U: Q4 i. A6 D% r: `( K2 i  j. L0 F
    Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    $ ?7 {5 ]  ~" e  e1 y( e1 J
    4 I7 s: z' M3 c* Q5 B2 lpd.date_range('20200101','20200331',# _; _5 _# D+ I) S
                  freq=pd.offsets.MonthEnd())5 Y. {/ i; @0 Z

    7 K8 X: Z3 x' k4 TOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')+ I  i8 c- v' V- W) H6 S
    $ C; j4 ~5 l! E2 B1 y9 M# a6 [! A
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    ! B; {* D: P& _' W7 V& e4 k+ V2 QOut[98]:
    / t. L% T$ D2 o2 b4 Z/ k3 ODatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    2 g8 V1 H3 ^6 m; V               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    " j& C( S1 _: `4 p/ h              dtype='datetime64[ns]', freq='B')
    ! `8 j* Z* j2 _3 h% q4 f; `5 n# l3 ^  R% [
    pd.date_range('20200101','20200201',8 ~1 K6 u5 E; }: B0 {. d/ U
                  freq=pd.offsets.CDay(weekmask='Mon'))# H+ G4 T" C6 ?1 O

    4 N% d6 h0 f: z) ?Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    * L/ k7 {- D; }
    * \* V  R* a. }9 ?pd.date_range('20200101','20200201'," d8 C0 X. e( W# A# f* ?
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))( n' o; F" C9 G5 c+ ?
    . {* z! \5 f$ L$ I, k$ O- y' \! X
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')( ^; R5 k, S3 S8 {

    & I& O# X6 B0 G8 I1# q0 D4 m- a5 [
    2
    + ~9 d& ^% m( G+ h$ `3! y  {7 K) T. O4 N! {$ w# |8 E
    49 y( [$ v$ |# e' Y8 K, c% P/ \
    58 X5 h* X! o! }" G
    6
    9 S0 f9 w' ]' t- h$ Z/ Z7% f9 B: u  c4 C9 w9 Z) c5 U
    8( g0 X4 ^# K8 G7 e+ O- b# o  |
    9
    6 e; K( p7 Q  K# t; s) ?& J10/ W* X2 y3 S7 i, X
    11, ?9 k+ c% ^- ]5 L2 G: V' h1 x4 N# h* ^
    12
    # D6 K2 Z) J0 g  k' a4 ~7 m/ ~137 O1 y8 v5 h" |: _
    14. Y  f! u4 Z5 s/ m% j
    15
    ( u( ^) A4 N/ A' c16+ e! S" ^$ K+ P+ i
    17' _) L1 @( D  U/ l1 i
    18
    ; h6 V2 ^4 A* l2 c  t3 D2 o+ a! s193 t# g6 q# F0 P7 ]% i, U
    20
    8 o- n1 S# k7 \/ s. Q21  j7 L% C" }  j, a
    221 ~8 ~) p8 {5 u% F3 i+ I+ l' e
    23- Y! ?# {* k# c4 z1 S7 o
    24
    2 S- S! B3 `4 _& K1 e( A9 M, S25
    1 K3 f! `0 _' S& [. x% @; p【CAUTION】关于时区问题的说明
    ! m$ r9 L6 {5 W8 b1 S2 E2 O& B  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
    8 v  j( s- T; {2 G9 I6 g5 {( D8 j5 V; F
    10.5、时序中的滑窗与分组9 E0 P3 w% h5 H% M1 ^3 g7 q6 T
    10.5.1 滑动窗口. ~: f- v7 l1 f2 J/ ?4 q
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:* G5 x6 S3 o9 L% v* S0 d

      V, q3 r# X2 N7 d9 o* Timport matplotlib.pyplot as plt  [& [* ^$ a- O
    idx = pd.date_range('20200101', '20201231', freq='B')7 d8 ]. c$ I0 L% E4 e) F$ P: X/ ~
    np.random.seed(2020)
    8 r+ t8 z4 Y3 w; {: [+ V4 \: N9 {" F$ y. G6 B( G% g! d
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加8 O$ G" L; Q. e3 E' v! c3 q5 u8 B5 m
    s = pd.Series(data,index=idx)
    ' {% ^  U2 }4 D6 u% {1 T4 Ls.head()
      V) A1 V  _. F) jOut[106]:
    + b0 r/ I6 S' p3 c# A9 @2020-01-01   -1! j" f& Y+ F: k6 j
    2020-01-02   -2  u" F# v" ]( z+ n/ U
    2020-01-03   -1
    # |% |2 C+ X) g; Q+ `2020-01-06   -17 ?1 q7 M7 E! k
    2020-01-07   -2
    & Q5 ^* [* q8 F6 Q# K  n# TFreq: B, dtype: int32
    1 w0 G) X8 S. ^r = s.rolling('30D')# rolling可以指定freq或者offset对象
    & s' y8 z: P9 u2 ^* o/ f$ h
    6 n5 c! M$ U4 z  Gplt.plot(s) # 蓝色线3 `3 x0 S7 z, L. Z
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]% ^! F. Z% L" u* v" C6 [9 j9 d' [8 H
    plt.title('BOLL LINES')
    * C1 X5 ?) u, `+ S& aOut[109]: Text(0.5, 1.0, 'BOLL LINES')$ p8 |5 H/ }5 G5 A4 v
    ' w& l. r$ O; M
    plt.plot(r.mean()) #橙色线; c: q: W  O. H. Z" }; S
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    * L( Q- \' [' U5 P' u1 z- P) p) b- G2 |$ O2 s( R
    plt.plot(r.mean()+r.std()*2) # 绿色线9 q6 d' I3 X1 p0 ^7 B
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    % }5 z! p) H! |0 K9 ?- {5 u0 q2 K) x, @9 o; c- F' e8 T' [  H, w
    plt.plot(r.mean()-r.std()*2) # 红色线7 n. R& @" n; t( N& h4 ~& e
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    ( S8 p" h2 H1 T% H% Z$ `6 L4 g" R: q
    * d1 X: _1 d9 g16 e. \7 ^! J( N' X  `' m
    2
    ( z* w5 v; r+ Z4 B/ f3
    1 b2 j% x& J9 f& D% N. w/ r0 d4
    / n1 C# c  p! a- E1 {6 a- Q3 \5
    4 [" R0 R5 v" X4 _+ j: a; }68 W! d9 I" u' u: w1 l  M! p5 `0 a
    7
    / o" a7 l% G5 v7 v: ^, @+ p81 P4 j1 q: c! i% S
    9
    2 d1 M: A" M( Q/ `" N/ Q1 R8 @10
    ' g& C6 c/ I- {) s+ Z11
    2 J: Z3 T' F8 ~2 @* r) u: }12% k& w4 @5 u7 C7 A0 _
    13
    - V4 v: Q: P2 r% g& m14
    1 L0 m  k2 r  t3 ^$ B. W15
    " ]6 `9 `* l2 |$ h% @165 |" P9 r5 P' y( _& q
    174 s$ }1 O1 t, [) W
    18" C7 i1 T" ^8 d) G2 f7 i% R
    19) E/ r, q- F* y
    20
    , K% j) C3 {9 V6 o' H* w) E/ }# I+ f21
    . f* k* `4 e% w  v- E/ n- m3 }22  h7 Q( J$ z% w$ F% n1 F
    23
    ' E' r/ ]; B1 ~24
    ; U" N( h3 T( d( Q( e( b' ^; D25+ W) O" ~9 {7 ^
    26
    * {6 a7 j! n; i% z5 p3 ^27
    5 p, R2 x  q+ v5 `, [' k9 }28; n2 U+ Z1 Q! ?; v
    29
    " `/ ?! s/ j5 A( w1 Q* |2 x+ p' x2 v( {1 P5 J4 c5 G
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。& G; L: X& M& S3 ~
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。  H- _! u. R1 Q* M" w# u# m
    * r9 M8 i# \. y: y
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
    , b( w  o7 r( W# ebday_sum=select_bday.rolling(7,min_periods=1).sum()  k/ L) X/ n7 M5 {  }+ X) |
    result=bday_sum.reindex().ffill()2 o% ^9 p+ D! M7 y4 ]( U
    result
    ; E5 ^6 p* X  {$ M
    , F9 I+ }0 @1 m0 }6 _2020-01-01     -1.0
    - T6 g3 p$ R' H+ e2 z2020-01-02     -3.0
    ! w& a- X( A8 V8 t# Q5 \# f2020-01-03     -4.0
    # D0 U$ S9 }  \( z7 [9 U2020-01-06     -5.0. x# I  `% M0 }+ x9 S8 m
    2020-01-07     -7.0
    * c) u) n" ]7 I/ N/ y5 X! H              ...  + n6 E: g2 c9 M) m
    2020-12-25    136.0- |1 ]$ b% u. u* L
    2020-12-28    133.0
    8 |) O6 x2 \6 C; ^2020-12-29    131.0
    8 x0 [& O9 T0 J2 H: r2020-12-30    130.0* }( i9 C/ D1 U( t" {, ]% ~
    2020-12-31    128.01 A# F' r7 s9 y6 k' G) P
    Freq: B, Length: 262, dtype: float64
    ( R9 [+ n% w6 T! u) i: q
    2 b! ^4 t. y: t; f( o, ]18 R) ]9 ?% y6 u9 I
    2; A" F. U5 S; t7 U/ j
    3
    ) g# J9 P, f! ~% Q2 M44 i9 ^& k4 I/ Y8 A* |) _9 A( ]
    5
      N6 i; C0 x1 @8 f  c4 l6
    # A3 I, w0 O6 B. ]& W+ h72 ?/ K. k4 z' D
    8
    9 f# Z" N, u- I. n% R3 C: ^9( a  p  g- T+ k8 w
    10
    ; R' {- g2 f5 z3 f11
    5 r0 ?9 @3 V" W- p# H0 ?9 n& ]124 {& @6 I; U0 F3 h" K5 P
    136 v. B% v. L" e
    14
    4 e8 y" b: G- A3 v; h0 F15' ^$ @  J0 [1 a& d9 C. ~* c
    16) x7 W8 c3 ?% N  K9 t
    17
    ; o( e0 t1 }/ ]" S  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    . I' c( \. P; g4 \" i; x. x* f$ L6 z4 j" ^0 f
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    % `! c+ `5 B7 p1 R) n3 g5 G; h( ~0 f: K% p6 p4 x9 q
    s.shift(freq='50D').head()4 T4 ]7 l9 Q0 o9 ~. Q
    Out[113]:
    $ V3 Z* T  k% z3 Z) h( ?, [6 ~. @2020-02-20   -1
    6 W* ^4 {6 k- p5 P2020-02-21   -2
    , [( [+ F6 ~9 K4 S  B2020-02-22   -1
    7 J+ n. m8 v! P. _2020-02-25   -17 m; S1 i8 K6 A" ^! _
    2020-02-26   -2
    ) s. ]& I& r, m: x8 J' h0 ~dtype: int32
    ' s7 E6 l3 Z: |9 y13 i: n; ]" F/ x1 F1 \
    2
    6 q3 W$ D8 k0 G/ i- h: e* ?. `3* ^. H$ K  O0 B0 _
    4
    + l+ A/ I( `3 l8 B% `; P: E9 t; V59 w9 E2 s( }' ~4 r. A/ P
    61 C+ X# S" h- Q3 N: W  Q5 c
    7! }  B+ l  ^' T, I
    8
    . M, c& Y/ G! X( L  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    5 g3 p7 e- Z) k8 i
    + e% i# y- P# {+ q% i" P5 P9 W* Mmy_series = pd.Series(s.index)4 Y! I# g: ?5 N) n9 w7 |" @! i
    my_series.head()6 m4 M0 m+ Q  F' E( G( F
    Out[115]:
    ( d: L% c* E$ \2 F- m0   2020-01-01
    $ b/ M: g7 x8 S1   2020-01-026 Z& L/ \; E4 _4 Q3 E6 Z" |
    2   2020-01-032 T  ^* K' g! t  x8 }' X: y
    3   2020-01-061 U% E0 A- G7 z; [; F3 g- ]9 x& a
    4   2020-01-07- C$ q3 }" h) p, a- O+ t: ~/ ~
    dtype: datetime64[ns]5 W8 |1 c6 F9 w1 }" @. a% k
    + d! W9 W8 q0 |
    my_series.diff(1).head()+ S4 y% m6 [8 I- r" k6 v
    Out[116]: ' _3 N0 L7 K) [' e
    0      NaT( p8 {, g. L0 |; h/ K& D% n& t# R
    1   1 days
    2 B6 [1 R( l9 a7 i6 {6 u2   1 days
    3 u) b* x" G; b! R3   3 days
    . O$ x& |* S7 x/ }5 v, H5 i4   1 days7 M1 v5 _2 T4 M1 F6 V; q& I
    dtype: timedelta64[ns]
    + p$ `' ?; f- h/ K8 P5 c& T. O) ], q& t" X2 k. e
    1
    0 l# q/ C$ T2 v7 k! A2
    6 M8 r2 P- |4 G! ]; w3" Z4 V1 ]; E9 u  ]1 t8 E* Y; c
    4
    9 T: z/ G  W  t- s52 E9 P4 e7 |+ G$ F
    68 x4 u! Z( R# Y* m* m, T
    77 `1 x- T) z% n8 h% `, A0 {
    8' T) ?2 _/ N2 g" v: L
    9
    3 Z8 S/ \+ F* e8 ]10
    3 D' A6 R3 z. @4 T0 L11
    + A1 U) o& }1 }" n120 s/ e* U% u6 i0 X% |8 k7 t& r& }
    13
    ( l5 P* M( B- ]2 G( q14
    0 \3 N3 H  I5 i6 K- F15, a* R% R+ X. L/ w, ^% J/ j
    162 j! y* e) l0 x3 f# d8 e
    17
    ! r9 f' Q$ `: V6 Z* ]( P18
    * U+ a; \* |! B2 z10.5.2 重采样7 j% @& z4 f7 L# v
      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)% i. w' Z& T7 [+ H; X- s
    常用参数有:
    7 @, \2 U# b1 T, A1 H* s3 Z* p$ [
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象, y% ^# a) R) W5 m3 a6 F; _) W% w
    axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    + C  }% _. D$ S) u1 p- Zclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    9 L, g7 P( [+ t$ ~label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    - n7 f7 k/ y4 Nconvention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    # G; A3 ^* z' T9 J5 ~on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    . q6 N6 N2 c. ~level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    . D7 R/ R3 W  w  a4 G( eorigin参数有5种取值:. S" X: _6 t" l! {; B6 N
    ‘epoch’:从 1970-01-01开始算起) t1 @: q9 D7 B+ \2 r; F* X$ d
    ‘start’:原点是时间序列的第一个值" u0 O( N, y8 d2 W6 s; ~0 Z
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。, X1 d. t4 d  J/ b1 y3 c
    'end':原点是时间序列的最后一个值(1.3.0版本才有)# ?9 L+ F: c) n3 Y$ q9 P) Y% w
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    : S$ _) T7 z- {. D3 G" u4 coffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。1 k( ]9 U$ ^2 P) {
      closed和计算有关,label和显示有关,closed才有开闭。4 R/ _7 B3 j9 I4 X# N0 W" |: `* P
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。0 V  X( T" ^7 r: b9 J$ \

    : X3 x& S7 A% o  h) c重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:% }" e8 w- f) v; `) O
    s.resample('10D').mean().head()
    $ w$ s: E2 @4 @9 e; }Out[117]: " X7 k) G2 D$ v- V) l& O0 _* p/ k
    2020-01-01   -2.000000
    * x/ @' ?* A5 x4 e2020-01-11   -3.1666673 X+ Z% _0 E! [: c" M2 g3 [) P, z
    2020-01-21   -3.6250007 ^$ q3 R' C3 i2 b- ?
    2020-01-31   -4.0000000 E5 D- p: s& f8 o1 I0 {" D. ]
    2020-02-10   -0.375000
      V/ e$ Z) l3 U' ^! [3 oFreq: 10D, dtype: float64* `5 Q7 T+ {4 K) b
    11 E0 D' l! B2 _5 P
    2% W, N3 ]! C+ Q8 N" j7 f7 ?# Y% a
    3
    4 f. R. d" L) d% F4
    7 o/ u/ N6 B: d5
    ( ~: @/ z0 R$ w) b2 S6: A0 M# n. Z/ d& T, R
    74 B0 \: {+ o* ~- i. q( d
    80 S/ `# l1 G& p2 t
    可以通过apply方法自定义处理函数:
    8 |/ P. Y+ ]6 ss.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
    : s9 y8 l5 e' b! T% n; I; D' G
    / l( M8 ~- j4 Q* O% x, _5 |Out[118]:   |9 a- ]6 l4 U* L* K7 B, A
    2020-01-01    3
    " i5 n$ e: Q* D+ @& Z2020-01-11    4
    7 d4 C  l$ k  E0 `9 G' |1 m2020-01-21    44 y6 R, I( k/ B3 a9 F/ i# z$ l! A
    2020-01-31    2
    ! Q+ M0 S5 U0 w) R9 S6 D2020-02-10    4
    / q# g) B0 O  I& h: ^Freq: 10D, dtype: int329 J% U, d- n) q* i: r; \0 V% R
    1
    + c) r( ~2 `& p. _2
    9 o/ c+ ^! a+ }3* V3 C: ], Y- V: {5 d
    45 q5 \7 D8 v) }7 r3 V. I. b
    54 |6 N. t$ o% b2 c) g# {  [/ m
    64 d  ]3 [' D# }6 t" y
    7# g- [+ L/ i6 k0 k
    8
    ) e% Z& l) a; p" m% h& B( |9
    , B. v& E$ E5 ^1 ?  n7 x& k  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:1 k1 |" W+ K$ `$ @8 l0 q. W, t( U

    & C, v& _: `( b& @9 u% d- Hidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s'): M7 B- X1 X/ I, ~: W1 f# ~' f& @
    data = np.random.randint(-1,2,len(idx)).cumsum()
    ; r: i# \  L! R& o0 ps = pd.Series(data,index=idx)' c( H! V4 |- S" i8 u
    s.head()# N* o& ], P& A7 Z) t! |6 g% w8 w
    7 J$ W! j0 J4 r8 M7 N
    Out[122]: % I# |! ]- R; W1 x" B. J
    2020-01-01 08:26:35   -1) {+ M$ A( y' h3 p$ y1 S
    2020-01-01 08:27:52   -1
    1 o" \: a. p% T! ]7 u, m2020-01-01 08:29:09   -2
    8 o% ]3 V! u  u2020-01-01 08:30:26   -3
    4 G* L7 A' L. S. {2020-01-01 08:31:43   -4
    : _! ~6 ^, y9 [: t' IFreq: 77S, dtype: int32
    $ i- |' ]: P: z: L: M% K4 @12 H) O" [0 O% Q; C& i8 L
    2. N; ?/ z7 P& J
    3
    $ Q' T, G5 {2 S7 N4 g# _4# h) F* E0 F% {/ j
    5
    2 _- Z/ e0 l& {+ a6$ M; Z- G6 ~- p
    7; L: V; Y( E& w( t, v# {
    83 W4 U1 o! M. p7 U  \
    93 x$ @3 [& ?& G7 h
    109 j! ~, `3 G( \
    115 k$ ?- n" n9 r& i- M
    12' }5 c* n# J0 e0 E' g  a) [! a
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:4 |+ W/ q" {( o- t
    " V/ o6 [% c) e" L. d5 q
    s.resample('7min').mean().head()
    * q: x' b; U8 `3 k8 K# r3 HOut[123]:
    ! _- R, v9 |" x* s& [- g: @2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值# m' V# |' @! k) b
    2020-01-01 08:31:00   -2.600000
    , D2 I: E0 e% F, K2020-01-01 08:38:00   -2.166667; D0 W7 F, q3 B2 I
    2020-01-01 08:45:00    0.200000
    # e0 ^' n$ s6 _7 X& j2020-01-01 08:52:00    2.833333
    $ {+ {- J$ x6 A: M2 o  fFreq: 7T, dtype: float64$ v0 f; q2 i1 V2 m) q! g7 J8 d
    18 `+ x- c# u2 {6 s
    2; n" h( ^* q7 Z: ]7 R
    33 A( [" @; W. `0 n+ u  k
    4
    ! d( p( k9 T8 k: J* {9 }5
    - g1 T0 j! h- y4 ^; B- c6
    6 J+ z2 w4 ]7 y+ v' v; {( z0 z( j7* t& C/ G1 |( V0 K$ T
    8
      [1 r% [* Z; `  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:3 w' ~& Y+ M5 `4 w! @
    7 m1 j1 h5 Q- N2 d5 `% s% Y$ H
    s.resample('7min', origin='start').mean().head()
    6 \8 ^* J! E6 s, U0 J' yOut[124]: . f0 c1 e/ k5 j" }& R9 }# P
    2020-01-01 08:26:35   -2.333333
    . Q/ W3 q5 x  C0 J2020-01-01 08:33:35   -2.4000000 I2 j& T7 G* P0 _/ b
    2020-01-01 08:40:35   -1.333333
    / [* b0 A% o! s' n/ f2020-01-01 08:47:35    1.200000# c2 g" `* r4 b3 |
    2020-01-01 08:54:35    3.166667
    . h4 T' k3 U$ {( tFreq: 7T, dtype: float64
    & V) \; V1 D  x, R5 [1
    ( G$ R( l! ~+ t3 w% t7 ?2
    ) \4 L2 l" o" t8 ?) N/ U+ y- [1 `3
    + X  V) C7 r3 p0 `4
    3 b3 z) g6 Y. b5
    # }, P. |; |& U& T: H6
    ; z0 a, Y0 C8 t* e% y3 O1 g7
    9 V) h6 A1 b: }$ }. A4 w( D8" Y4 W0 n0 [1 Y( s
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。) [$ H3 r2 a) n9 t9 W1 O9 n4 F
    ; l  k3 a* s- r' C/ t( R! [6 w3 i$ R' Y
    s = pd.Series(np.random.randint(2,size=366),
    6 s; I' x" t/ \4 R/ @8 K              index=pd.date_range('2020-01-01',/ D9 `5 D3 i9 l( |
                                      '2020-12-31'))
    * L+ k; ^+ o% |0 d/ {9 X* R3 }+ K6 }& {1 J
    5 T. R9 w% }: H! b; j
    s.resample('M').mean().head()
    ; V7 }% b6 Q  s4 tOut[126]: 7 c$ l3 r  C. S" {  `& E6 Z0 m% h
    2020-01-31    0.4516131 E- E2 ]/ L+ N1 A6 u; U' d3 E
    2020-02-29    0.448276* {! q6 X! g$ @8 {9 v5 q8 k
    2020-03-31    0.516129* P4 K. Q/ x& X3 n4 _; l
    2020-04-30    0.566667! _) q# T8 K. p
    2020-05-31    0.4516132 U/ T! T2 r( ~# v+ q3 f3 F
    Freq: M, dtype: float64( d( U* v9 \8 @, v, ]; S4 U* n0 c+ w5 c$ v
    ; b7 j, R" G* ?7 ^" n4 `! K1 ~
    s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
    " w: @; `( X8 c3 T+ L1 yOut[127]: * {" \7 |# U/ R+ s; f1 I3 r+ X' a# k
    2020-01-01    0.4516131 @' _2 z5 P( B) w
    2020-02-01    0.4482766 P0 d/ E, S9 s  l; n
    2020-03-01    0.516129  P$ w! B4 r9 j8 a& n
    2020-04-01    0.566667
    0 b# C* I, B2 F2020-05-01    0.451613* u0 P' k6 K# B; Y8 g% f' o. J
    Freq: MS, dtype: float64
    - w- H9 ~: C8 P5 M
    4 Y; d( L! _7 R' F8 ?1( A; [+ T8 `  x# D$ t1 M. R
    2+ n+ a) u6 B% T: |
    32 U% [+ y+ L! G8 P  o# ?
    4$ E$ G: F3 ~- C, Z, @  H2 g
    5
    - p2 _0 k3 V+ K: v5 x6- a8 }: i( r& l; e- E5 M6 D9 Z
    79 W" S/ m3 U1 u0 {; Y* X6 b
    8, ?  p4 m$ i* ^; y5 x  Y2 N
    9, V9 z; h# V% i' [: U7 t
    108 d* ?; k4 @# S5 G  D, V  E. Q
    11
    ; s2 q1 y# s2 |- P8 V* l3 G12
    / K, A1 M6 ^# Y4 m$ d! J- k# m13; u5 A% ~  {2 L: p! P0 t* {+ F9 t
    142 n) W/ O; u4 `& K! C7 E. Y8 [0 ~' ^- P
    15
    ; C/ l/ R( ]1 w, C% D" _16, M  i" r: @4 |1 t! Q! m
    17
    ; G% i6 X1 U" C# G- i18& B6 `# A+ T( p. {- ~1 f* R. E+ l
    19
    3 U* p! Y0 H- j8 H5 C20
    / h4 W% s2 ?8 ?& R21% O3 f" C$ |5 L, |/ K0 Y- N# `  \
    22
    / y9 G" x1 ~; S* T; E( Y: u  m* P对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:5 ^% }6 S5 C' k4 @9 ^( q# |9 ?
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],+ Y: m; O& h. ~9 W! T
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    ! `& m% @# T1 H. l- |df = pd.DataFrame(d)+ r5 k4 b3 R0 B. Z. N$ G0 n
    df['week_starting'] = pd.date_range('01/01/2018',8 O8 P) V8 X9 |) ]5 A
                                        periods=8,/ h8 @& |* O8 [5 }* A7 k; m* F
                                        freq='W')! W. M$ L% K9 R# |) `1 n
    df
    4 b7 e: u/ o* `* j   price  volume week_starting
    + S' ]0 e- H9 P1 ~( h+ E0     10      50    2018-01-072 s3 P+ ]( H9 z# D1 O: f1 r1 ]
    1     11      60    2018-01-144 k6 v) e5 U/ Z5 j8 h+ v0 r7 A" A/ s
    2      9      40    2018-01-21% t. u; K; B$ {9 H% M) {6 p3 a
    3     13     100    2018-01-28. Y/ Y; e# D+ V; N3 i- C7 I
    4     14      50    2018-02-04% {+ I% w9 A% f* T
    5     18     100    2018-02-11
    + y% m- K1 Y5 ^% f  s( t. z6     17      40    2018-02-181 u8 M/ Y+ h: }9 W* [+ l, @
    7     19      50    2018-02-25- `9 S, g, F& h9 P  j6 J4 O
    df.resample('M', on='week_starting').mean()
    ; y& [6 W; N2 B5 `) ?) Y               price  volume% z" b: h  k- v8 p9 V- e/ `8 j
    week_starting
    , H, ?( [# c5 r# Z7 U2018-01-31     10.75    62.5- f; \% V7 J7 ~6 e
    2018-02-28     17.00    60.0/ l1 R7 f4 ]2 z- u- F" F) J
    # h; j' M+ w) p! J& R2 W2 R  Y
    1
    # z7 K2 E, f# N  Z% e. {, ?9 J2
    ! g" X' p" h% T2 p  z3
    5 W' F, @' x/ d* @- U0 S42 g* N1 o' t6 m; i8 X1 C* m
    57 W! [: m$ {/ ]
    6* I9 |- G5 |. U( F, C4 Q
    7
    ( R( e: H( {; ~8
    5 ^/ M9 z+ O2 W: y9: P5 ^+ b" y3 k# k  Z  K% W0 J
    10! `  g$ V1 Y. x. D: X4 ?. F# `6 k, g
    11- _; R. E5 E% b5 q
    12
    ! k* k. {9 W$ ]% K# u% h* W' g132 R- h- a0 q+ s* U: P
    14' M8 A$ P( t: N# K0 d% j/ h
    15
    - t) q% f+ u5 R# _1 x16
    # }7 _# ]" h. n0 S# _17: Q8 P" _  L% `4 U: h) }
    18
    ( n: ?/ H' M2 _9 d199 ^+ t- N2 }1 z3 N3 k
    20
    ; x+ A4 D5 P2 i7 [21
    ; }% p: N) B- M6 c对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
    5 ~  `" a; f- f  mdays = pd.date_range('1/1/2000', periods=4, freq='D'). S( S, @5 e8 }+ E. J
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    ; X, Z' K% @( g. E/ @) g* r      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    ( U2 K+ U$ g, Z4 I) A% ^df2 = pd.DataFrame(& g% |+ ^4 W" X9 R. o( o, X
        d2,
    7 a: i+ M, x% ]6 G2 r( U3 O* F) Z* `    index=pd.MultiIndex.from_product($ z" R! ^! s) F: ]* n
            [days, ['morning', 'afternoon']]. W2 x! i& L9 h
        )
    % F/ O, y* e, z% v: T& |)( r8 E1 `0 s: C5 I* l# F' X
    df2" M' i1 q: O  l
                          price  volume& p# m5 a2 ^. ~0 I# a
    2000-01-01 morning       10      50
    ) k( U% G' q2 [' D1 k1 Y# i           afternoon     11      60
    ' B$ m2 o- t% E5 s0 O  }1 {% O2000-01-02 morning        9      40
    7 e7 `, _  S. K" U           afternoon     13     100
    * L2 k8 ^( X- p- T0 _7 ?2000-01-03 morning       14      50/ [( d' e" Q( z1 \3 h- C% e' w/ i
               afternoon     18     100$ t' [8 @; t* G- L& w. g5 W
    2000-01-04 morning       17      40) }4 X* S( r  m$ }* I6 r) `; b, e
               afternoon     19      50% M, f/ m, {+ P: ]
    df2.resample('D', level=0).sum()% Z1 }: \* K  b
                price  volume
    % @& F: J4 V  L4 y2000-01-01     21     110
    4 W* P; D7 D8 m. `% X9 J2000-01-02     22     140
    7 C: C. s7 m9 {2000-01-03     32     1502 g1 K, S- B" ]' a  A
    2000-01-04     36      90
    , L  O7 a. P; S& h7 _8 G7 r$ F' R1 C  C$ f" k# V
    1
    2 g1 ?) ]/ [! S4 I. S2/ K1 y; Y! |( T4 {" _! n
    3
    - Q: W* X6 S% u# ~4! h8 J" y4 W$ z* j) Q
    5& ~$ |2 X- ?3 K  x- `9 H. R) t7 u; O+ _
    6# q: p+ y* X; P. N
    7
    8 E  N/ t0 D1 V8+ w, E+ P3 j, P* j1 D0 z
    9
    2 ^# `& m0 [: k5 J# O* E10
    + o8 q; N2 }+ M- v" x- C11: u: T9 W1 I1 {5 ]
    124 x" n- u8 f; B% |
    13+ e' J' v; C' A# {7 _
    14  {" I. v$ d* A8 l+ j
    156 Y2 d+ `1 I( l# K) ^' R5 G
    16
    ) P) C1 T' ^9 x. ?) H. n17# z! k( L. x# m& T: N1 x
    18
    8 t3 e; D, `" s! O; c1 m19
    - \" w- C( ]7 h7 X! f20
    2 r- X, o  v0 `8 z9 ~21; W/ y; ]$ Y8 `% a0 \3 X6 S% W1 R
    22% n, _/ |8 ]0 L
    23
    , b) ?5 ^* H: a* N. ^" H6 \240 c% y5 O8 b" a" r7 v3 H# x4 F) I! @
    25
    : E) x" r( o% y5 `& r根据固定时间戳调整 bin 的开始:
    ) l+ ^$ w( q9 u' ^. d0 ~start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'. d% B5 w$ o2 b% D
    rng = pd.date_range(start, end, freq='7min')
    9 Y  w' L8 v6 {) Q! Fts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    * h8 T& m1 F  @% s% A- [  Y) e+ a4 [ts
    9 Z; F8 Z& }- A! [2000-10-01 23:30:00     0
    / G% U! s4 e9 b0 B. F2000-10-01 23:37:00     3% q* e, I8 |* p, p& P- q, ]
    2000-10-01 23:44:00     6: k6 N: l+ s, g. Q
    2000-10-01 23:51:00     9
    2 I5 ~5 P: q7 D1 m+ a; D6 P2000-10-01 23:58:00    12
    . S; s% U! q+ O, S; l4 j2000-10-02 00:05:00    15
    1 D, ?- t( ~- n0 `  H2 |5 U/ V3 D2000-10-02 00:12:00    18. u; `  u! V3 O6 `* d8 }
    2000-10-02 00:19:00    21! q! \% S: P, B0 S- c
    2000-10-02 00:26:00    24
    * w1 n& P8 @' L# [5 z2 KFreq: 7T, dtype: int64
    , x: p# z! g, z- p. T; G: _7 a7 L  o8 B9 c: \& Z' k* p! d  h. N; g
    ts.resample('17min').sum()
    - I4 H' F8 S' ~2 \" d4 I  v- `8 N7 _4 m2000-10-01 23:14:00     05 p( U6 n% U0 J9 q  R3 ^7 }
    2000-10-01 23:31:00     9
    3 ?  F3 t2 j6 m% |3 L+ x2000-10-01 23:48:00    21
      v3 X6 w) |1 H% d( v" `0 F2000-10-02 00:05:00    549 v6 n7 E" ?5 |* e( W0 f4 i/ V+ C' o
    2000-10-02 00:22:00    24) [  |& r8 T, g" w8 P2 o
    Freq: 17T, dtype: int646 c5 u4 I" _- v$ |9 G9 A$ P
    6 T1 R, }6 L& M2 |, t
    ts.resample('17min', origin='epoch').sum()! D+ W9 g: i' d
    2000-10-01 23:18:00     0
    " j) F1 K0 K" K+ n& q) c* A2000-10-01 23:35:00    18
    9 c+ i9 K( n. v! ?6 ~$ S2000-10-01 23:52:00    271 I  w; D4 Q5 A% g  M
    2000-10-02 00:09:00    39
    ; R; Q2 g9 @. W$ n5 i  C) v: ^- w4 \2000-10-02 00:26:00    24
    ! V$ T' ?/ }) h6 [9 n3 `Freq: 17T, dtype: int64
    4 D* }& M! w: n, Z6 W5 _! {
    4 e8 X5 y2 b- ats.resample('17min', origin='2000-01-01').sum()3 w7 a2 t6 g) j: ?0 z- j
    2000-10-01 23:24:00     3$ |- f$ |: H/ f. W) J9 H
    2000-10-01 23:41:00    15
    , b( I( G7 q( c0 I8 _, A2000-10-01 23:58:00    45
    - G2 {. {; W2 g& P% _2000-10-02 00:15:00    45- U$ I$ y0 f) P2 \9 e, D
    Freq: 17T, dtype: int64
    4 s8 a, i$ k" a/ y1 P4 f+ j$ Y! ~" I" i6 y# {. x/ h
    1! F. }! E' b- e: }. q2 {  @
    2
    % Q( T! O7 j* J39 Y( _. B" \% }% I) m8 m7 O
    4
    0 U! F3 H4 H! t7 o9 m5
    # l$ d& f/ u5 f2 x6 }( S6 u9 I8 i6- |- G8 y; H9 n: P3 G# f
    7$ O) I/ C0 [6 F8 c! t" ?
    8
    4 P5 h. u3 u+ u8 F( x9
    % H4 p) Z( W; O  `% A) i6 N10( h7 i. L) p( {1 `: g0 h
    11
    / {! _: ~% ]! u12
    " f" Y0 j- p+ P. U. P4 G13
    + J- ]1 [3 |2 ^0 N9 z14: W  ]/ E1 q/ {" A; @2 ~( r
    15
    9 W; o1 `$ T; h  ?3 r: A) L16
    6 F& k0 [% A# Q- u% l. d/ U174 i' K( u* P6 m8 l, p
    18$ f. q; T% M# ^! B" M  N- x
    19! Q6 O! W1 W: V4 n( R. V
    20
    8 E! ?, ]# O( ~8 Q4 y21
    6 L0 r9 \* N9 `7 x7 q, r( y/ O22
    0 d/ H/ e7 t8 W9 k) u23
    4 V) Y+ H" h7 _1 e" ]& s1 c0 T24
    5 F2 p0 w0 r. @/ {! m% Q* T/ _+ k# k25
    2 Z* d! B+ c, ~. r' n9 V+ t26
    1 X( v; G% R. ?% P  t5 [7 m' }  V9 m. A27
      e, j9 Z! E% }$ v28
    % S' D- Z' q+ W# d% K* @# F6 t29
    4 L. c; M4 J9 D9 Q30
    : }* D) ]' O" H7 u: @31
    0 A/ p/ F* L6 W1 D32
      B1 V% j& l3 j! h! v33& t/ F4 k) T3 }" l  m$ s. l
    34
    / k" z) D) E2 u" T7 r35
    " q$ u; Q! O& X2 f364 d3 b! L' D/ Q, L6 l8 k
    372 O$ ^: Q: h: p% ^
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    ) ]  \8 [5 W: J+ j6 ^% cts.resample('17min', origin='start').sum()0 x" y5 {$ i1 h* y' c
    ts.resample('17min', offset='23h30min').sum()1 d" A% K; t- |. z0 T3 M* e4 [+ c
    2000-10-01 23:30:00     9& R. \3 {  s, T* H
    2000-10-01 23:47:00    21
    ; W! n$ v' l: P* g- i# M0 [2000-10-02 00:04:00    54
    ; ?' m; d& H9 y' j2000-10-02 00:21:00    24( v# h+ z5 s  m/ V, v  O- u' A& X0 A
    Freq: 17T, dtype: int64( o. W+ n$ a7 E( T" [5 i
    1& n5 X* k5 l. L8 u8 [
    2
    ( t# o1 y9 b4 M: i36 c& f& |/ B1 j
    4" v. g6 P0 ~% ?
    5
    0 O' n$ @8 g( S- R6, B: y, M9 W" C0 Q
    7& |% O  H0 N8 e+ N4 {5 G+ U
    10.6 练习' Y. O$ _' N$ r
    Ex1:太阳辐射数据集
    + J7 u/ |; u6 [& t$ r4 g! {5 J, p9 A现有一份关于太阳辐射的数据集:6 V. N% r9 l; `4 \& n% k) y
    * ]/ X& R5 t9 x, I1 q
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    1 j" Q/ f1 |1 B7 r6 ]df.head(3)4 @5 t1 s/ N) Y& k
    8 V& m! ~- `3 w9 B1 i3 @) q
    Out[129]:
    ( z( c. N9 Y! o+ l8 L                    Data      Time  Radiation  Temperature
    3 @% M7 R- o0 [( c0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    ' k1 K7 d) i+ ^8 A4 \1  9/29/2016 12:00:00 AM  23:50:23       1.21           48% p' w  I3 c. J  F7 h4 p
    2  9/29/2016 12:00:00 AM  23:45:26       1.23           48" h6 c  ^& ?/ s, v
    12 E3 J/ q- W1 W0 n" A
    2
    : M$ O/ _7 B; N: X' X7 f2 m* B3* ?6 H0 s* J) e+ y# j
    46 z2 n. \: p0 `2 @0 K+ w" L7 s
    5- O$ [& r5 p0 a6 |
    6" c& R! x6 q+ c: g0 H, h1 h( |
    7
    ) a7 b: }( b" _9 h$ U8
    ! {, B  b2 \$ x+ j- H) o' E5 |将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    6 q$ w" w- A" i每条记录时间的间隔显然并不一致,请解决如下问题:9 I2 f9 J, I6 ?! `. N' M
    找出间隔时间的前三个最大值所对应的三组时间戳。0 |5 J  v! I& L% N* |
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。4 f. m, V; R8 \4 o( u
    求如下指标对应的Series:5 O. X# m2 ^* {7 S
    温度与辐射量的6小时滑动相关系数
    # n  F1 r$ v" @4 j以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    7 C: K. S5 H5 f! |每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    / C0 u# p" L9 |) G8 `import numpy as np( }, j' M1 r/ H: n
    import pandas as pd
    9 U% Z; H. m; X# N; A$ d1
    8 h1 E3 e7 v1 R( ^, s$ z2 {2
    8 j% f. O  {2 Y6 C8 q( J& r. n1 I将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。* X0 P0 J; |) p7 K% Z
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列0 r2 Q- l6 e9 T3 ]+ J% B0 u& V. l
    times=pd.to_timedelta(df.Time)& z$ W2 |% e+ D7 d1 l
    df.Data=data+times6 a0 Z  j" ^; O) h: S5 i
    del df['Time']
    ( M+ ?9 y4 g( O. Xdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    : L+ X, y5 I/ |/ i! V9 l1 {4 Ydf
    7 N; U2 n* C8 ?                                        Radiation        Temperature* ~* w/ v( C. ^5 c, j% A( D6 e
    Data               
    . x- ~2 z4 _8 v; U) S2016-09-01 00:00:08                2.58                51
    4 W  }& U/ W5 H; ~  i: ]2016-09-01 00:05:10                2.83                51+ _; z5 ]; g5 x( R/ k
    2016-09-01 00:20:06                2.16                512 m" ?' S$ x$ F
    2016-09-01 00:25:05                2.21                51
    3 l' B! M( d- F4 T. g! X8 H2016-09-01 00:30:09                2.25                51) l0 h+ G( t1 Z8 H0 ^! J6 [' ]
    ...        ...        ...
    & s5 m* g! A% [2016-12-31 23:35:02                1.22                41
    7 s+ \/ m2 P3 ^1 x- C. U  z- B8 B2016-12-31 23:40:01                1.21                41! l2 k1 R" y$ B+ f
    2016-12-31 23:45:04                1.21                42
    0 I" Z) W* X5 R, Q8 D  A2016-12-31 23:50:03                1.19                419 J5 H2 Q7 w+ E; S  w
    2016-12-31 23:55:01                1.21                41
    5 A4 v' f% o7 ~6 M0 a0 `
    6 N3 G7 @  u2 @# H" J+ G# ?1
    : {9 o) g: t( K, E# v2& d' p# J/ o+ o
    3
      S1 g5 B3 g. U& C$ u6 H4- l, |* d4 g7 n1 _3 m
    5. f7 Z0 z, `1 X+ |
    6
    7 y% ^* c* [! ]% b9 O+ t, B76 h* t% p5 d) c4 ?- T7 M* G: y
    8
    ; o* Q8 P, _' s& l, n5 j' W* k9 b* T9& M* S+ W" [8 y: Z, J
    10
    : u% r$ d2 D! h11' t& V2 [% E5 v( Z' f" W; O. P
    12! f" C/ C2 p% ?: b& F( g9 S/ H4 {
    13
    . j4 V: _7 t! P+ U7 q, P14
    : Z$ y2 d$ p: Q$ ]. ~' ?15. ^7 _8 E9 b" \" m9 l, Y
    16( l0 N. u9 i, Z' |( ^. c# [' g( q8 S" L
    170 p! Y+ J* i5 h* E0 @
    18
    3 z- t/ \$ N1 z! y2 k# y- i19
    ! r+ l: a2 _' j; q% Z' z$ \每条记录时间的间隔显然并不一致,请解决如下问题:
    / H+ H) W. y3 k找出间隔时间的前三个最大值所对应的三组时间戳。
    * y/ @$ y; B5 U& t# 第一次做错了,不是找三组时间戳; z: ?/ M8 Y% X' `% ?6 H' G% n
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    9 p$ X5 Q& i  w; F( y1 Vdf.reset_index().Data[idxmax3,idxmax3-1]
    0 L6 @3 c4 \7 V: f; E; M# V% n) z  O# i& z: `% H% |* p. I  ~  S% V
    25923   2016-12-08 11:10:42! w1 Y+ N* F0 {
    24522   2016-12-01 00:00:02
    8 Q! ]7 ~3 n$ S9 o7417    2016-10-01 00:00:19
      O' `- v$ i! U$ k3 W) K  w; p8 vName: Data, dtype: datetime64[ns]
    . t5 r0 ?# j7 J" P! F11 l# A( J. V4 O; Q! s3 ~+ ]2 T+ z
    2
    6 F# T5 b! A. W* {, s6 _; m( H7 e3
    ; G( B8 Z! J, g' P$ e3 W+ ?4
    , T+ k& {1 F7 i( D57 Q# C9 m+ e- X' [0 s' [, t
    6
      n" x+ K% V' L7/ t6 s' C  ^; b6 A8 u
    8) C" P4 N* ]2 ?9 e
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    8 |# B: `( {( g& g: Ulist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    : l& l9 p6 s( d/ M. X' ?+ k$ u8 s; N$ f* A! p% u
    [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),$ V8 i) Y$ S  y1 ~
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),6 s7 W! r/ `+ H1 J- D- x6 j% h9 z1 k
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
    - }- h8 M7 C+ {1
    : L5 N3 Q+ q) E/ L6 ~, q2
    8 l& ^" ?9 p6 L' J( ?3
    6 V  T2 @# u# }6 C& r4# l/ N' v- }+ V7 O/ M% s1 \
    5
    $ O. z+ q" ]& K; J% \66 S' U; x$ X% t: m0 G6 j
    参考答案:7 _. x3 m* g+ G

    8 ~$ g) ~" q, P6 k. Ns = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
    8 u: N& m! r0 j& r. o4 fmax_3 = s.nlargest(3).index
    0 H# n+ [. A* m' Edf.index[max_3.union(max_3-1)]
    - V! z6 y; S  {+ n/ t1 J2 R( f6 I- {& l
    Out[215]:
    $ L. y% @6 M' E5 `: GDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    8 T9 D+ a5 F$ _$ C  r& E( ?% P. d               '2016-11-29 19:05:02', '2016-12-01 00:00:02',
    # b+ ~. O; I: N! z, H               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
    7 d5 \. k# O5 Z              dtype='datetime64[ns]', name='Datetime', freq=None): a- D  Q+ |! {( R' A# E5 m7 G
    1
    + m) |) |- x/ u7 F* l* `/ O2) `- U8 v$ a4 H5 X6 w
    3
    $ B9 `& [( I; m0 d4; X: w) {& I' b: s0 o, m
    5
    7 ?2 d/ [; r9 d! I! t6
    + @/ {+ R1 S$ B) I& u7
    * n( {& h6 H8 v- X8- K0 R# I" E4 O
    98 ^) i" m+ q/ R3 n+ r" d1 \: H, d
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。; A$ T0 O7 J7 C: f! N
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间4 y0 ]3 p9 K: c
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)/ X" o: {' m7 _* m( H0 `
    s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)  e, h' |5 Y* C: L, @2 x1 t- j# d
    # `6 B; M; p" m. d- T
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)" F* j8 H5 L7 C9 b( u$ e0 D
    1
    ; @& }2 \5 o+ g2. k5 y: ~  T1 @" C' O
    3  L/ m# j0 J8 i( R1 p
    4
    ' ~3 S, p7 I3 x- e/ T$ @5
    - E$ R8 C; ^; C/ W2 L%pylab inline
    9 f, _0 o7 a) b4 O2 A5 Z# M& T_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)1 D" X6 A: m) d3 w( \1 g' p
    plt.xlabel(' Timedelta')2 O) v2 F& n8 I( ?% M
    plt.title(" Timedelta of solar")+ M$ k3 o! H% }
    1
    1 R3 |' i# K8 B# M! o9 S) X2  @$ L' o1 A5 V* f" M" v
    3
    ( l7 E9 n' ?$ j40 i' l& c& x- D- ]) I6 o
    # T* H" @& Y9 B5 w) h
    8 J* K8 D- t* I/ G! s) u
    求如下指标对应的Series:8 b* n! _3 g/ Z- U& p6 `" {
    温度与辐射量的6小时滑动相关系数1 B; d: X2 j# x1 h+ O
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列; w: T2 q4 [3 t1 o6 ]/ c8 M
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    , S$ Z) ^. F6 p& zdf.Radiation.rolling('6H').corr(df.Temperature).tail()8 X& |3 u3 a* u* j
    , ]9 p" H; M! q; u, Q
    Data! T# d1 F! q# I$ U6 f
    2016-12-31 23:35:02    0.416187
    7 v  J& p' u: b4 E2 Q2 c2016-12-31 23:40:01    0.416565  P1 E8 h4 s2 T8 i+ }' |' k, n! J
    2016-12-31 23:45:04    0.328574
    7 r7 m, h: K) ]3 l. a% W( f" ?2016-12-31 23:50:03    0.261883; K% D% s3 d7 _/ l
    2016-12-31 23:55:01    0.262406! c3 @. Z, U+ t) T  H* I
    dtype: float64- x1 i: Z  c' P4 S- C
    1
    9 k  n) a5 W& k0 O4 \2
    + {% g- p' q: G& L3. `! g9 v# Y: X" _# w$ t$ e
    4
      l4 d; _* P7 y0 F% P5
    ) k1 T# C1 f, q6% B! P6 ?. h. W1 J
    70 u0 f; Y) s1 t6 p
    8
    1 B9 E* ?6 d9 \9 F# U9$ C% b# {7 @6 r4 s, B3 K5 ]
    df['Temperature'].resample('6H',offset='3H').mean().head()
    ! P# p+ c0 L; G8 |: x
    7 ]: K- C3 g" G+ b  s2 H2 `, zData
    , _! q/ a8 _3 Y2016-08-31 21:00:00    51.218750+ |: \1 Z: L  x0 B& B" n
    2016-09-01 03:00:00    50.033333; L6 p& `; P( u2 J0 M
    2016-09-01 09:00:00    59.379310
    # Y& B5 n) s& j9 y$ N2016-09-01 15:00:00    57.9843757 t( P9 W" k& S7 V  d* _
    2016-09-01 21:00:00    51.393939
    $ S2 f4 ^2 c5 k4 ~, k8 RFreq: 6H, Name: Temperature, dtype: float64
    , t) k" U5 {+ h  Q9 F14 O" ?2 N5 u! E
    2: n, R) O3 l$ l. X1 Z8 B# S
    3, q% z7 s% f( C/ I! f/ D
    45 }# {7 D5 ]* }$ v3 q. P: M
    5
    6 K! I. b/ ^  ^# E6; `, i: M. S  y# F  L! U; C! e, n
    7, l! j. M$ m% c* \9 A. M; ]' e% T
    8$ b5 m1 o" u7 ]" J" b9 j& g& N
    9. S3 X  a) m7 ~; R- Z( e
    最后一题参考答案:
    2 s" x& B$ }( T; u. w* u4 ~/ r
    - ]5 O* [4 W/ x, Q# Q; j7 U# 非常慢
    6 a8 V% s3 f8 Z$ U: omy_dt = df.index.shift(freq='-6H')
    + K* X  d) E( q- w) Pint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]5 X6 }! o* p+ z. g: n! j
    int_loc = np.array(int_loc).reshape(-1)
    2 h  h$ m9 @7 A& e; u+ Z/ Gres = df.Radiation.iloc[int_loc]. {* y- x! z  L9 E% h# p0 V- m
    res.index = df.index
      Y7 n; |# l( Xres.tail(3)- k) i. ^+ ~' i# W6 E* `8 b$ h7 \
    1
    ) B, S  S" D$ f' P2
    ' o- @" X) r% p( C1 \9 I3$ K& a9 g; x2 s
    4
    2 m! _7 b4 H7 ~1 f. s' v5 m5
    5 ?  p# D; G( r/ f! P7 K66 D: r& J7 J4 G+ R- l& a
    7  D% z7 a- g* `, \
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级# \7 b$ Z, e) ?7 i5 Q; L7 C
    target = pd.DataFrame($ W5 i1 u. t# q/ }
        {2 A8 f( a  A) V  L+ V9 O2 S3 V
            "Time": df.index.shift(freq='-6H'),
    7 G2 Y9 [2 ?( M1 L* \3 y2 m& i        "Datetime": df.index,
    : ?: Q4 B  T1 d+ p# Z- s  c8 ~    }- Z  ?% M0 ~4 h# g; |. J- ~8 ]6 J
    )* r. S3 z4 Y2 j0 D+ V* ?2 }

    ( m  n9 i; h4 \0 g& Ares = pd.merge_asof(& d8 G2 K. t' l6 W
        target,
    * k$ E* z0 J3 _0 [# E    df.reset_index().rename(columns={"Datetime": "Time"}),0 p7 q) k/ [& h; a! ?3 o9 [
        left_on="Time",! L0 K5 a6 k  P( q( x& r7 H
        right_on="Time",
    5 m, v4 g9 j( o! A    direction="nearest"% S' Q" r4 ^; B4 a0 R, ?
    ).set_index("Datetime").Radiation: R# w: @* T' E9 u8 x0 m1 h

    & p, ~) ]3 U+ ~* L- `7 o) ^& i, F: yres.tail(3)
    ) R+ [# V9 [6 X( G; J" [4 QOut[224]: , j  A5 g# m2 G8 h! @+ t! n
    Datetime
    : v4 V& I5 e& i0 h1 ~2016-12-31 23:45:04    9.333 i. p+ Q. W5 N& C% D: W
    2016-12-31 23:50:03    8.49
    : |  B+ i" e1 `! o! e) v2016-12-31 23:55:01    5.844 p; t5 I  r0 P  e! b" }
    Name: Radiation, dtype: float64; b9 V/ R3 l% h" C# d! n' @

    . S8 l6 R5 Q0 E  n4 u12 g, T: x. D5 E3 D2 d4 u
    2
    . D5 Y$ k, H3 m33 P+ t5 s6 G8 o! I
    4
    * |$ ~6 H7 D4 R/ H+ D, z+ J5& c& s4 t5 a4 d  G- W
    6
    0 @- s: v+ C$ t3 {& ]7! F9 I- W! d* Z2 ^8 L( O
    8# m' S9 i% N2 n5 o% f% [
    94 v! n2 ^0 g( e( U, v3 Y5 a
    10
    % S/ g* [( M0 G( C  [11/ H$ a7 G, N. _3 g7 L. G3 x
    122 e' \  B- I: c" Y
    137 W- q- X7 G1 i7 @7 H% i2 y
    14
    ; j9 _/ b  S: h: ]  I* G15. ?: B+ ~. p8 q" J+ F
    16
    5 H! ^0 J1 O6 Q5 g/ U17
    , \5 o) s( H0 ]- U9 u18& s# @- Z" b8 k1 j9 m; b8 y2 S
    19
    7 P  l  j* m9 p( U) G+ X( K20. b* u6 [, x' C6 J% i7 |
    21
    ; `6 T4 U. r, E22: P3 O9 \; s7 ]; Z1 G* S- W
    23% K4 H6 C4 a* Z- ^2 h" \  A" V# W
    Ex2:水果销量数据集  A$ k  d' _* [2 v8 i) g( H$ L
    现有一份2019年每日水果销量记录表:0 g' ^- D" w; \( k# I
    * ^8 u" m' G; q& f
    df = pd.read_csv('../data/fruit.csv')( s0 ^; }" G" t5 b( h# w) m
    df.head(3)2 U2 r  M5 u* h7 ^2 s' c2 Z- D2 ^
    , F7 J7 K" a) r5 [3 V; n; M
    Out[131]:
    ) j9 i. x7 f/ E) X         Date  Fruit  Sale
    ! n7 c, o; }% G) S& s' ]0  2019-04-18  Peach    15& i2 r( J. }, G+ y. V
    1  2019-12-29  Peach    15; j. H$ ]( ^2 ~2 }; X& v
    2  2019-06-05  Peach    19
    ! s6 r1 ?4 I# Y! i( l1
    " Y: Z7 L. X6 D7 j( P! c0 K2; ~) t5 e/ R5 o$ b* Y8 C
    3" X$ O8 q" j4 _' I
    4
    ' k1 M( a7 l2 {( W- a( @! A% T: l53 a% n8 g6 _6 P6 N9 F
    6* b3 c' U& ?# Q2 W5 c# e3 `
    7; u* c# m; K7 x5 M6 Q
    83 N- j% t& L! N. }+ |
    统计如下指标:
    ; j4 Z  |9 }* L: J# b4 l每月上半月(15号及之前)与下半月葡萄销量的比值% x7 x! X1 N5 T2 P. I% e- r* K
    每月最后一天的生梨销量总和; q$ B/ v) B6 |; d9 K5 `# y
    每月最后一天工作日的生梨销量总和; }$ @  z4 t' A% i3 p! A
    每月最后五天的苹果销量均值
    ' I) S6 x2 ?- G, q2 W& t9 v按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。& N# ~- N$ R9 h) t% Y+ N. n5 ~
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    & ?- l  S% y) D6 U" Kimport numpy as np0 C! _% ^6 v; t  _6 e/ B/ `
    import pandas as pd
      k" W" G$ V2 Y3 L4 F1
    6 N( o- r4 s$ M2! y' X5 |6 D; k
    统计如下指标:9 l4 I$ x$ _  Q6 p) c( h8 k
    每月上半月(15号及之前)与下半月葡萄销量的比值
    $ o: x# y" s& ~" L每月最后一天的生梨销量总和
    ; d' r7 h: `- i, `; M" g每月最后一天工作日的生梨销量总和
    * n$ N4 L& c! Y+ j; Z0 c每月最后五天的苹果销量均值
    ; T! L+ x& \  o. }7 j* w) F# 每月上半月(15号及之前)与下半月葡萄销量的比值7 x: N3 p1 C4 I" A& c7 m) V
    df.Date=pd.to_datetime(df.Date)
    / G2 E  M4 J, Q: Z, L4 y5 W6 Tsale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    & X$ U# H9 k6 f! S& o2 Ssale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    , p2 }" A1 ]+ Dsale=pd.DataFrame(sale)5 m% Y, k* _8 L0 h" d
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    5 ?: o2 [: a3 X8 d! _8 t+ P                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    4 `0 r; C6 |' O( m9 ]  jsale.head() # 每个月上下半月的销量
    7 d3 W- c+ h8 h) ]' D3 }
    6 }$ j" U' R8 F$ x  Month        15Days        Sale
    / w2 j4 o5 f! v3 F3 c& ?0        1        False        10503
    + _6 H: G; z2 S2 e* t' _6 @( T' K1        1        True        12341
    : g6 x3 k& P2 x" b4 W( }2        2        False        10001
    1 d% D7 b9 F! J; t( D) y3        2        True        10106, J) }% B' b0 J2 f- d
    4        3        False        12814& h6 z/ @( A5 `9 Z3 h. x0 x5 j9 A

      d  L9 Z2 y. c- ~; l# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
    * `' E* w% F3 C8 L; {) ^7 d% Qsale.groupby(sale['Month'])['Sale'].agg(
    - y! R  [7 |3 U# o2 I- J( ~: r7 u0 ~  Q                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())3 t/ t2 S, P: G- x& K# x

    & V1 A/ M- y5 w$ I! s9 R0 GMonth
    5 j; F! }' M  G2 R" @  f1     1.1749988 h+ l+ U4 R# a  ^4 n8 h+ o, m
    2     1.0104997 o% ^2 g+ O5 T3 W% }% l, V
    3     0.7763380 t, ^6 E$ C, y) t
    4     1.026345$ i2 i" J5 n9 [
    5     0.900534; f: |0 t1 M1 T# p
    6     0.980136
    + _2 Y* m0 t# \2 O7 t7     1.350960! D9 j" c* L: h, c. O, J9 Z" I9 y
    8     1.091584
    7 X# ]) ?$ d# Y: W9     1.1165087 a" w! G" Z" Y; l+ {8 p+ u( \
    10    1.0207844 E' e( C$ P5 K
    11    1.275911* ~( m6 n/ C+ ]. ~+ a& @
    12    0.989662
    ' |* s* g5 g* u2 t3 k$ u4 }) sName: Sale, dtype: float64  ^# v: X% s6 N

    & }% I3 d1 U6 e/ J2 C1; e1 \) A1 q% v
    26 r9 A. }! m4 M
    3- k2 L  b7 a) _! j- y$ |
    4
    # [( l- n3 _8 F* G' D/ c) u5
    . H4 x3 ^8 D) e6 U68 a# `$ e) ^& a* v
    7# s; t1 r5 g# i( o4 Y3 @' y
    8
    ( W: N- \4 D5 `) W9
    ' H- _  W+ v  u4 a- X10
    & P. W3 [4 T, ^! I11. y, k4 |$ ^$ u
    12
    2 O$ l" m9 l, `! K# R0 ^13
    # D/ R2 B7 p& ^14
    + k1 |  t5 j5 ?  O8 b* [. j* q158 [1 _6 D( \6 K) a5 C% i! B
    16
    % s" ], \0 O& X  I) u# c1 S17
      d* D# V( E/ A  J5 n7 Q18+ t2 Z! c" i9 ^  h6 h% E, k7 t
    19$ o0 D0 X3 c2 d
    20
    ; }4 |" L$ E  ?* l) ^) h21# c2 D; n% m) x4 e( C+ T* Q
    22- Y; Z3 i6 y3 R3 b, ]% g
    23  W/ o9 p5 [% f$ U6 O; g$ n
    24
    * v; D: o' }5 u! y! R- i3 a% ?25; }! g! M- o0 q) w5 H8 L
    26
    ! e4 L/ |% ]9 a+ Q/ q7 l; U27! k; U+ ?' H4 C- F9 J9 Q9 e; f! D
    28
    2 u0 [1 ?$ O3 q* q( d* I" `( \29& u( I  P, S9 Z! A* l
    30
    6 k0 ?. D6 X$ J( e+ g9 z" q31
      u7 v4 f  Z& G0 r5 e320 u: K- A. |" G2 Z& H! L
    33
    4 ~2 n* W) `3 m1 q/ x6 I342 ~* b) k* c$ X  l) _& a% d
    # 每月最后一天的生梨销量总和8 a! a' x/ h& k
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()0 \& b; s3 N7 l# Q5 A

    " O2 Y) B( ?! m0 R6 j$ jDate
    ( P8 s' A/ @  W4 m4 M' F2019-01-31    847
    - Y9 Z& D4 D9 ~; i+ @2019-02-28    774
    ; H2 z( N$ H3 Q. N! ]' P2 r+ M4 [) u2019-03-31    761
    - |4 a+ I/ N% E& J) l2019-04-30    648
    . O0 g4 }/ H7 Q/ d- `2 Q/ v2019-05-31    616* P5 R" r  z! _3 d
    1$ @3 m  W3 b( Q# L% F- F) M. O
    2
    5 e8 V+ `. E2 e6 A3
    ( b3 E, w3 Q/ d47 R* C5 b! d4 c3 v' ?
    5
    ( G9 b4 V' q, m) T, ]8 T. R! Z/ f65 a. ]7 I0 G" y
    75 x) u' p: m7 S
    8- A8 Y; E1 K$ |5 x& S) J  r, F
    9
    9 `( Y# [' B2 f! E* n, m: \# 每月最后一天工作日的生梨销量总和0 W1 C+ Q6 s& g
    ls=df.Date+pd.offsets.BMonthEnd()* Y$ u  v% f2 Y; Y9 [4 {$ Q
    my_filter=pd.to_datetime(ls.unique())) ]+ H" e9 `- S' }0 e% B: M* g
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()  L2 `' N3 g3 ]: j, K: q+ l

    ; f  c# Q" o% b& d$ lDate7 P2 H2 a6 y3 ?1 H5 z! g
    2019-01-31     847
    % E8 y# k3 A2 X9 p5 q8 U2019-02-28     7747 D) V* e, H( ^
    2019-03-29     510
    ! W5 I: k' f7 a2 O2019-04-30     648( v  F, w4 H5 h7 Y; [! F
    2019-05-31     616- A8 ]& ~. D) s, U/ |( v. ~  k
    1- |+ x% s, q& F5 H: r- G0 C1 ?
    2
    2 a( F/ U) f( q6 ~) \1 C; ?3
    3 C, M: V1 a$ s$ `: p4 E; {. F/ J41 b6 u& D# S6 J" r7 `
    5
    * n" e2 }. |2 P" A1 o5 V6
    3 ^* a$ I. i% G2 m. F& n  |6 W7
    . i" R$ b; H/ @3 e) h* V8
    8 Z+ s5 X, h7 a. o5 G$ X+ M93 o, Q: |/ D3 x- x& t, ]
    104 e/ R; c/ T" P; @" f- i
    11+ ?. `: Z6 @' n$ F" k) _7 H7 L
    # 每月最后五天的苹果销量均值
    % R" q! w! o  Q* ystart, end = '2019-01-01', '2019-12-31'  y: A& |) n: M9 r) _- |
    end = pd.date_range(start, end, freq='M')# Y9 r7 D$ n8 D& w- e) D0 q
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差  E: a- a* I# {' p" [7 {0 |

    ; Q+ ?* I" C$ h3 z, ^td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    5 G# ?4 w' Z# w, B# Y! btd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    5 J5 G! g  H7 h8 @1 W& |: a) ^end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    ! q& H/ v7 J$ b' B+ e" Y0 \, ]
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    1 X5 \! _9 s& m" T7 lapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()! f" k! O% O5 J. ~# h9 M* p

    0 P+ h3 m! o7 M8 F" bDate
    * _; p; _; s- d8 e8 K1     65.313725' V# o7 S( S0 n& }
    2     54.061538
    % m' A, u# m. D# T. m( J) ]7 L3     59.325581# M% J/ V6 r" h8 B4 c
    4     65.795455# i6 o( M" f. w! q, \" u: d" I
    5     57.465116
    / k" J- b* W3 D! C: r$ m/ s2 d
    ! O6 {1 ]% F) x/ W" ^$ D0 X" f1
    4 O- V4 T+ o, X9 g( z5 m: T2
    / ^' l2 ~2 U1 B7 u3
      k# r; R- h3 v) L4- i4 |2 s1 Y7 [7 T( ?
    5. y' P- v" y% |+ n& e
    69 {  H8 j/ D2 t6 _% j- p# j  M9 M
    7
    1 l; G; E: z$ c3 C: Z* S8
    % ]. f$ W$ d$ e: H4 p9& `7 T$ h5 c  s( f+ S
    10
    : U$ I: u; R! o0 l  _11
    / I) Y* }7 h6 `) \/ B% `" \12- ]0 D1 E2 w+ C; \1 I3 V$ E  c  q
    13
    , p6 v6 M/ e) f8 |$ O140 [" K: h( e6 C" N- a
    15& U# e. x) [* K: Z+ ], Z, h4 H
    16
    ! B. r6 E, g  y9 e4 \: y17
    & T+ I: z8 g( V+ B" \) G1 {18
    1 [; `1 ~+ G! g5 K  I% ~# 参考答案:- T( l; k% |' \' [" V
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
      l  y, K% Y: i7 X- f4 a! |6 s- f" a            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)' C& c  q0 W6 ~  d
    6 a5 h+ X) Q- q. ~5 ?& W8 ~
    res = df.set_index('Date').loc[target_dt].reset_index(' m) _; F! V1 {! _  t; ^8 C& M
                ).query("Fruit == 'Apple'")
    ) h( P4 j6 f- J& J8 A6 a0 h# o+ u0 i
    res = res.groupby(res.Date.dt.month)['Sale'].mean(
    3 m$ c: h' [$ D, j* g            ).rename_axis('Month')! T& d" p7 Q3 q  \+ b1 S/ ^3 d7 a
    % ?/ X: ]/ s) S0 `) j* h2 F
    : x2 _: q1 o6 U* z6 e
    res.head()) {$ X  I* f: l0 Z) g' t- }8 R
    Out[236]:
    1 p( P/ ~+ r; l& [Month
    ( r  E7 G1 r1 A2 x: J% `1    65.313725
    $ m/ P+ a7 [. }0 U7 N; p2 y9 Q2    54.061538' [5 R/ j% K7 o6 D$ J9 Y4 N3 R6 s
    3    59.325581
    / Q$ k1 x: g6 H" F) Y. ^6 r4    65.7954552 J5 w; D  T. C( X% }* X
    5    57.4651169 y$ P" ^6 g- I1 x# I
    Name: Sale, dtype: float64# s( r% n# S/ L4 e( v

    * a7 C2 K0 G$ [1
    4 R/ q* t" h7 s+ V- D2
    3 M7 L- ?+ U; R3 e8 T) g. l3
    8 d4 w9 f/ t8 S1 v; q" B, L4
    : V4 v5 t3 U, R+ B5( W5 O0 ^$ l9 t  m4 T2 [4 ?
    6
    ) b3 B4 q0 f& R" l7& a! F3 ?+ D5 c$ y) H# W
    8
    ) v( J! z9 J5 a9 e9
    % Q1 W; `3 D4 ^. J/ c9 r0 t% X10
    1 v" h; A, |/ b6 Y/ @6 a- ~  v11/ u) _: e3 c$ p8 H2 _/ K1 X
    121 r' {* ?" ~6 i+ |; L) {5 Z
    135 g* M% X; W& x5 _; X
    14! P& A+ p. v2 {& J% J
    15
    3 C+ ~0 Y$ r. X% s$ O2 p4 M  e6 [+ w16
    6 u& J$ _1 Z) v- x) q- x7 D% z+ F! I17( Q/ X6 F, G5 s: w( `$ B: b! b
    18
    - L1 g7 w! l5 r- \2 t19
    0 Y# R+ J# ~7 C( W" m! r20; N4 n0 p5 H( Y6 g) }# Y
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。  V" {! p4 O2 h6 W
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
    ; o3 E4 w  d& h" P                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
    ; P% i, U7 }; J                                        7 ?+ i8 R# Q; z% d4 W
    result=result.unstack(1).rename_axis(index={'Date':'Month'},; D: N6 `& s! h9 }
                     columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.( N4 d# K" C1 f5 V! A1 Y, a
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1), c' D, [: n4 ^( e7 ]
    result.head() # 索引名有空再改吧
    9 j3 H; _, _6 w! {! u! }0 z5 o
    * Z" q; W3 c# x* p- B6 `" X7 e          Week        0        1        2        3        4        5        6
    " X0 q- c% a1 o) A$ c( i9 JFruit Month                                                        7 L2 n+ \; Q7 y" b# k
    Apple        1        46        50        50        45        32        42        235 j1 V8 V5 @, j4 B2 K% C; g
    Banana        1        27        29        24        42        36        24        356 f) o+ C/ x+ a" |6 O* G1 s+ I% J
    Grape        1        42        75        53        63        36        57        46( P3 o% j- N9 q9 B" I, |! Q
    Peach        1        67        78        73        88        59        49        72( l7 t6 U2 _4 j8 c, o& Q
    Pear        1        39        69        51        54        48        36        40
    # B5 @* r4 B. T* ~% H$ f7 K1
    1 `' n# V" a. B2 U  w2
    6 f0 o( N6 W3 ]# x: b33 P3 F9 U& `4 h, y
    4
    " D# D& m' z" P; y; i! b: u5' K9 S9 A7 R' z- K1 L( u5 f
    6! X8 Q- ^, e7 V( V: G: W5 s- \
    78 {! z! i6 p- f8 N
    8
    : g) d- Y4 C! M92 n# g3 J7 r3 T9 z5 `  o1 A
    10& A# m+ |2 ]) O1 w/ t' J2 f
    111 R) U9 k9 W) `( p3 G
    12
    2 E  i5 S( B* G( N: A9 T; j# R13
    $ e0 _2 D* t; ^# n, j, R7 d14
    , ]4 _- J& F) t7 q3 F, l4 t+ z2 \151 `! h! L, J& I6 z. ^, s
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。; s5 c+ I) q# j) S$ [$ ?) w8 \: V
    # 工作日苹果销量按日期排序
    / W- [3 g* }; J% `4 E0 W% H0 qselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()' Z2 I' v; ]/ r0 c
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
    . \8 ~7 A+ J  G5 z/ [# Wselect_bday.head()
    ! Q3 W! I0 G- ]6 Z# g* m' g( ?' e+ H( j! m5 n# C
    Date
    8 `2 q5 c# Y2 O$ c" d; [2019-01-01    189
    1 [& [5 g$ W9 D1 E3 v* J! p2019-01-02    482  e% \+ z0 A, ]: H  E) E) w* x: ]
    2019-01-03    890/ z5 m8 z$ O: V/ v/ y, Z
    2019-01-04    550
    6 d: _/ T& q; E  a$ `9 h2019-01-07    494% ~% `% y$ L6 X/ p" u  v  k

    ' ]& s% J% h3 l; _# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。2 C$ [# x; M  y7 |1 G0 h; U, Q
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    5 Z$ W. d7 ]2 ~- s& ~9 h
    / Q' r' y: Y$ h5 w, o6 M2 Y9 j3 X* TDate
    6 i% F7 X" z9 t5 ]) o3 p7 x% B2019-01-01    189.000000& g4 f* b3 }8 ]7 \
    2019-01-02    335.500000( [4 y4 B0 `2 J1 f* h. w9 R
    2019-01-03    520.333333  e* s; m/ T- g4 I
    2019-01-04    527.7500001 \, Q4 }" Q# l; |7 u9 x1 z0 |
    2019-01-05    527.750000# D: J( a4 T( r+ c( y  S: [& j7 x: ~$ B
    ; m4 R; ^8 s0 d& Z
    ————————————————
    : x/ j) A2 m7 ^版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ' C" R7 F! ~( z  i原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    7 D$ L0 s4 U# L! V3 V" c& D2 S, |( f/ ~

      f2 X( G- S$ i  c) g4 S
    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-10-8 10:44 , Processed in 1.502779 second(s), 51 queries .

    回顶部