QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2827|回复: 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
      A7 P# G" _7 M9 U

    ; b$ w) Y# k6 _, u: L
    3 Y" w6 G" s! ^+ Q, W& h0 S文章目录$ F* X' m7 c' Q+ \
    第八章 文本数据
    $ I9 r) h# `# Q: Y. C4 p" n! c8.1 str对象! x. F# D4 a" v. R2 \/ W
    8.1.1 str对象的设计意图; A% G# V- T( ^2 M# f4 b6 z
    8.1.3 string类型
    # A  {$ c& I- h! S8.2 正则表达式基础
    ' H3 U# U5 C% n- h, ]8.2.1 . 一般字符的匹配) H1 h+ j" @* j: U2 [* w
    8.2.2 元字符基础
    6 P% b' m& \8 @) i+ \% [+ }8.2.3 简写字符集
    / v/ i& M/ S& d+ }4 E: h: H; s! E6 ~8.3 文本处理的五类操作+ U: D: s" @  s1 T1 i8 E% B+ x
    8.3.1 `str.split `拆分
    1 E( A( J+ q3 g9 O8.3.2 `str.join` 或 `str.cat `合并
    ) [( k8 d3 W- i& K9 q: _8.3.3 匹配
    $ m; ]( m2 m) I9 I2 s2 O; O  p8.3.5 提取
    . g+ s: o( |  R7 n3 W  g& ]8 N5 H8.4、常用字符串函数* z; Z% {+ |- y9 [2 z/ s5 v
    8.4.1 字母型函数6 V: A: y$ c$ |$ u, D/ j
    8.4.2 数值型函数
    . V6 d- u, ?5 i7 U8.4.3 统计型函数
    ( s* r9 s8 l. _0 K8.4.4 格式型函数
    - K: n  h* ]% x8.5 练习
    # }) \% n  N) ^3 }7 p/ oEx1:房屋信息数据集* O6 q  i' o- m8 @/ J3 i" P
    Ex2:《权力的游戏》剧本数据集
    0 e6 @6 H2 [) {, b8 x2 ^% J' a  u第九章 分类数据
    + f+ c/ U8 ^) n8 M( o8 x) X9.1 cat对象
    " ]5 K& R& k8 H& {/ G9.1.1 cat对象的属性
    : U0 U6 @2 R5 H4 j9.1.2 类别的增加、删除和修改& u( Y9 s6 `5 X
    9.2 有序分类, k5 ~5 T) o3 q( g4 l4 z* v8 W
    9.2.1 序的建立4 i1 l  f, Q& g2 F9 j
    9.2.2 排序和比较; x8 v  ^  U  j: t: u
    9.3 区间类别
    % F' s0 q  Z9 \' |+ S( G5 ^9.3.1 利用cut和qcut进行区间构造3 ~( H3 w+ l: \0 i, M
    9.3.2 一般区间的构造
    6 ?9 H. X* I0 V$ M6 l$ @9.3.3 区间的属性与方法
    6 G% H7 i: K& t9.4 练习
    5 b. O1 H# t; V/ V  g" K* d$ l6 zEx1: 统计未出现的类别! o$ a" `4 e- @
    Ex2: 钻石数据集
    & P* N1 G- r: D6 ^4 l第十章 时序数据
    . W) m/ x/ ?4 j10.1 时序中的基本对象
    8 `: A5 s3 n0 B7 R& w3 P; H10.2 时间戳% e4 q' f. s+ ?% Y
    10.2.1 Timestamp的构造与属性" r) M5 M2 }0 a3 _- f' M- T
    10.2.2 Datetime序列的生成
    % h- a- i+ ?2 N( t  l% z8 ?10.2.3 dt对象
    2 f$ [0 c  N) M1 u10.2.4 时间戳的切片与索引" g, t5 _) c/ v# Z
    10.3 时间差, V( n* U1 j& L9 y' d
    10.3.1 Timedelta的生成! j% ?* ]( V6 {: M' H
    10.2.2 Timedelta的运算
    9 y. E! i+ c0 ?5 w2 D+ g10.4 日期偏置$ Z: M, M5 g: A
    10.4.1 Offset对象2 O/ x& X( t: h8 h# i$ Q
    10.4.2 偏置字符串
    6 L  ?5 ^& X( L( Z  ?10.5、时序中的滑窗与分组
    & x/ o$ w- N; h+ `4 O& s3 y5 c10.5.1 滑动窗口
    0 Q5 C, m1 ^! ~! [( Y; x. m10.5.2 重采样  ?" b4 ^' D+ ]) c
    10.6 练习
    % K7 j+ \  I1 W5 B: yEx1:太阳辐射数据集% O- s0 E, z  T6 f8 ~" _
    Ex2:水果销量数据集
    - ^! u/ P/ H* o$ M7 R7 y  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网- S& q* f7 {- m
    传送门:8 c$ g' E+ q; z0 K- a, ?2 A/ B3 v

    ) M2 e% N& z( H  d2 F4 a7 ?datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
    - g/ o- M  g  m" l" mdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    & T* \! |+ I; s. B6 p$ W3 t第八章 文本数据
    ; s  A4 O" W2 f3 y! r8 d2 p8.1 str对象
    7 w4 v6 H! l$ S+ a" v( |' a6 n. y& A  P8.1.1 str对象的设计意图2 \, Z3 w2 Z* T& c$ S: P# ~
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    : r" b* [9 U2 |  I6 F7 w1 O; f. X) k
    var = 'abcd'8 W% m9 c4 \7 h' m, M6 L
    str.upper(var) # Python内置str模块: I+ d* J" x/ U3 O1 D
    Out[4]: 'ABCD') W) L2 q: D& }7 A) O7 _) z
    - A: F7 b9 X( P7 c" n- N6 |6 E
    s = pd.Series(['abcd', 'efg', 'hi'])6 k% h; V/ j7 [. `. m

    0 \  d- `' p! s4 C. As.str
    7 A( i& N# ]9 s4 K9 l: q5 g8 lOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    % e+ n: O0 ~- o$ U- C2 @3 f' X
    $ I% T" \- X2 W6 [! b% q3 Rs.str.upper() # pandas中str对象上的upper方法1 _  C- j  `' _! s& d
    Out[7]:
    / [. p/ C7 a4 B- E3 n0    ABCD1 ~. F- l$ a7 s' {( a5 E  H
    1     EFG
    . a. u: b4 N" Q9 r7 T2      HI1 R7 s7 V9 G, U$ G: P3 O. D" N
    dtype: object* c4 W1 d& k* _: X. i
    1
    2 L, g2 {8 i# o2
    # B, Z: x3 V# k- l5 S" T1 }3
    + N; Y! [- J* K7 H# G8 \- y4. q9 G4 X% M2 ]- M' O
    5) n. E8 d: Y! _/ z7 I
    6: X6 X+ G# L1 H
    78 C8 y6 s  ?& Z) \
    8
    4 Y* M3 |0 ?. Y7 F* B95 J+ X+ u5 }) I" z; h
    107 X) y0 p( @" I1 g
    11
    + n8 T, c# o! }12
    9 L( ?7 }7 y+ N/ u13( S2 v7 r# e- m! u) M/ S9 `5 m
    148 z8 z1 V% K$ f
    15$ ?& Q; ^! `5 ?5 [! v+ g
    8.1.2 []索引器7 h. k( k+ {7 C! D8 E( a
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。) e0 X8 B! r$ x* m) P
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:5 e" l' x' W+ |: x9 x
    * P6 m6 k7 B! b
    s.str[0]
    + o% T5 ^- c; JOut[10]:
    , i# R8 [5 Q  V0 d) g! j. C/ F0    a) W# @: U% \& n3 ^' u/ [' s
    1    e! G$ A; C$ T0 D  s& o* ?) Q+ |, h
    2    h
    7 Z9 t$ q+ |6 u# zdtype: object
    + K& n- o. g3 N0 o* @1 H4 D0 x; W" p* I3 ]1 l: ]/ W
    s.str[-1: 0: -2]7 D7 K% C2 \. x& ~3 g" ^3 G
    Out[11]:
    , N6 g# q: d  ]% N0    db
    6 u0 a# T) Z0 ~/ d6 }1     g
    0 t+ i6 C  u1 A7 P2     i* J) w0 U0 S* n1 Y
    dtype: object
    8 B( ^0 z$ G8 k- V) u- R8 t5 D0 \) _( l( E& {% Y( F3 b
    s.str[2]1 z( E# D4 w) y! a5 X7 g
    Out[12]: 8 a) ~$ a: o4 i2 E
    0      c  M; |% k, x) Z1 ~* O: K
    1      g( X  b: R& a$ Q
    2    NaN
    5 ^, i- N# I1 _% f8 I5 E1 t# G5 v3 edtype: object
    . @. j7 K9 N3 r6 G5 |; g. x7 Z: {$ t: x/ b
    1
      y) M8 V! k  j8 Y4 i2
    . Z1 Q1 k- z9 Y1 ^3
    ! l) F( C0 H( c- _& c4 a( s, {4
    " k7 {: E4 d- ?( c56 m' ]3 K0 j+ f8 i' L1 C: ]0 n! Z2 q
    6  G; @+ `0 `5 \5 o
    7
    , m$ p1 f1 ~8 ]8
    ; _# W$ P! y. B5 ^4 h9 i1 K97 L( J; n8 G% w3 M
    106 }9 _+ E+ Q8 Y. K; u
    11# {6 T3 U+ s* B) M/ \
    12. i  ~# {5 R8 v9 a
    133 F2 R1 a" T7 P/ E
    14% v" Y1 e4 w5 P9 q
    15
    # V) q8 r! c- \8 [163 A& @/ E$ \- A; m6 U! \# [; \
    17
    ' `& V8 U' x0 \; P5 N8 {184 Y: W7 j8 M. f( I* ?. ~. ^
    19
    5 p  w$ d# v2 B9 M3 E" r20$ Q' |: S4 A5 c2 T  [
    import numpy as np
    # l2 ^7 x  P( i) ^2 I( |! Mimport pandas as pd% h1 X% a* o3 c8 O$ \/ [- b% ^5 D

    1 N) {! }0 ]8 Xs = pd.Series(['abcd', 'efg', 'hi'])2 A9 n0 X. e9 Y) m  P
    s.str[0]
    + ?7 N9 u" |( S2 g, a& q3 U. L7 j1
    & ~+ a2 \2 t3 I  q  I& E! \7 }6 |2
    / {( \4 u' L9 x" i0 G! C3
    . g7 E8 Y0 d$ F, f1 {. j& x' D8 [4. L2 x0 {5 I- Y! |+ V
    5
    6 `, `( g5 D7 I  t: [0    a
    8 }2 T. {" J) {1    e6 T6 T( p0 y& a& V9 j7 g$ G! E
    2    h! ]4 O& j7 @2 Z* X. `! F
    dtype: object
    8 V5 h. n# d2 _* o& i1, F, x$ ~- a. `+ H. Y6 ^, n
    2
    ! r2 z* ~( [3 T) y- o3 X3
    8 o" r$ U' V4 b2 ~/ ]4
    ! B) X) g7 q# e( H! N8.1.3 string类型( |5 ?- r! d3 N, j( f
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    8 m6 @2 H) B( i  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    1 r( B* l& o, s, m9 T( H- Z3 v# o4 S0 N! _
    二者对于某些对象的 str 序列化方法不同。
    7 o' F9 F3 k% t2 i% G可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
    8 N: F, N3 h9 N7 es = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    / O- W( I4 _7 p$ s/ P2 _; es' N- K+ A7 L6 G: t( W- O2 s
    17 Z5 b0 z! B( C7 T2 N
    2
    - E; ^  u9 }8 k  k' c0    {1: 'temp_1', 2: 'temp_2'}
    " j; `# ~/ }2 B. B6 C1                        [a, b]  m) N+ [2 f! N5 ^" ^8 M
    2                           0.55 G1 p) {4 p1 h% j* Y2 `
    3                     my_string
    , @! P/ K! |6 C! v- p9 j7 hdtype: object
    2 h, }7 W" a: e7 K1( ~0 e5 `5 f. C7 t- k4 X
    2
    : q9 H+ S" v# W1 a; R# K31 ?  i4 J* ^5 C5 X5 P2 \# q0 E
    4
    * e: N) p* Y% J. V* H! Z4 v$ Q* o' C5
    9 ?) r" x- c0 Q- u$ j$ ]/ Es.str[1] # 对每个元素取[1]的操作- t+ ]! ]$ x: l% ?8 f. `
    1
    - C" E9 P& T2 c# t5 ^1 |0    temp_1
    + [) h2 j: a* w5 e0 o; L. q1         b: `( \9 b! b: o" m
    2       NaN; O1 G& I% i) M0 ]' I9 K7 R
    3         y
    2 _2 f: K  {1 A5 y! ~dtype: object6 e0 I8 U) V' o9 \) ~3 a4 |# @5 j* w/ U
    12 {3 q( M8 J$ B
    29 k, J) J- W' v( [
    3
    8 t- R$ M& {% B+ N! Q  F2 R0 ?, k43 D- k' A1 {" j, L$ O9 C8 \0 S: P* z
    59 v6 V8 ^1 Q0 \0 s4 a; v
    s.astype('string').str[1]
    4 h+ p( T" s9 s" y" Z% n16 V: z) B) A8 P( P$ S
    0    1) ~- Q. t' T( Y+ @4 P/ n  V6 y
    1    '6 {. t" Y6 X3 T* }6 ~
    2    .
    & }9 M/ r& r5 o8 z/ i9 h: r7 i3    y' S2 {* Y6 a: C, N0 t6 O, E" z  I
    dtype: string
    $ b* i  Q- j/ \& X+ M6 }1 m% S3 }1  e* I6 Y1 F- c' u
    28 b, r5 l9 [2 U
    3
    3 A  S! @' _. c6 f4. ^. W2 U# k: X% M
    5
    - W3 L8 @! k- G, b  t0 `) \除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:9 b* X3 K7 T. F6 m' \% ?! q

    9 H  j" S) m! u. S. J; i当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    ! }% M9 M4 d6 R; vstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。( X" A- I$ P' \
    string 类型是 Nullable 类型,但 object 不是6 j; V& E0 E. Q# O6 g
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    ! D/ |7 r& u3 _" x  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。5 O: H- g8 `' R, b
    s = pd.Series(['a'])
    # a2 J+ l7 }- U+ y0 E+ _7 _" W& Y8 [' J5 E2 V  g4 p
    s.str.len()
    ( {. C+ ~: A* g/ DOut[17]: ( C1 m$ @( Q. e2 y
    0    14 ~  t- L  L+ Y
    dtype: int64. }3 I/ t$ A  c5 U4 D# T- Z
    # z2 `6 I5 g8 ~$ l4 [
    s.astype('string').str.len()6 k, P* b* s- z9 y" n
    Out[18]:
    9 V' j7 A1 S& X. W' j; g0    1: }0 N4 O! X3 d/ b. @
    dtype: Int64' M# Y3 e. j' m9 w+ c+ H# i
    ) k9 F. x, Q, r5 Z3 H0 B( ^
    s == 'a'
    # V4 U6 d4 \/ WOut[19]: ; E4 m6 E6 p, h; P/ m; N% w5 m
    0    True
    7 G1 y$ ]8 n# K( Ydtype: bool) b* z! f7 l: K: G* y; A. M& a7 n

    9 _2 g5 g$ B; }s.astype('string') == 'a'
    ( N6 @1 c$ m: Q; g9 x1 y& l6 HOut[20]:
      I4 Z  B+ D5 M) l4 u4 ]0    True+ ^* F, O( X  w5 F( m
    dtype: boolean% j- S( X4 f" |

    ' b( `, r( l( @s = pd.Series(['a', np.nan]) # 带有缺失值
    / B0 c% o: a# t; K9 \" C$ v$ `$ ~& U  C/ J! C7 a
    s.str.len()1 ]+ C; q5 q  e2 d
    Out[22]:
    - s  p% Z; ^' e0    1.0
    & ]) t8 |7 j9 y( D1    NaN
    ! O$ g5 D0 ]- u" b) u0 [dtype: float644 F9 @# b% T. y

    0 }% _5 ]: ]7 U5 [. Ys.astype('string').str.len()
    - t: r: A2 m) a8 B2 C% }9 R* D  fOut[23]: ) ?/ R# t6 {  w8 d8 O
    0       1+ v* N8 \: y, Q8 l3 G  A( s
    1    <NA>: O4 l0 U. n* U; A/ x; n* f
    dtype: Int64
    / ?* M& }1 @6 @% Y
    ( w% y7 ?  u2 J) Js == 'a'0 |' h# b! c5 D2 [
    Out[24]: ; A5 d/ D& O; @# q0 q; s1 X& T$ }, t
    0     True
    . _3 U  N1 E3 t( T5 L/ x1    False
    % z3 C, K0 U9 Y, f; g/ B" Idtype: bool
    ( X5 h& X6 m3 ~! q! w$ g/ h9 V2 L$ y/ K/ Y
    s.astype('string') == 'a'
    + J5 v2 g: d, K% u5 Y) YOut[25]: % X* @0 s. X* T# g7 }+ t4 M! `0 m
    0    True
    - j3 a4 L+ l8 V7 x: `1    <NA>
    : K! _" C( c1 ?dtype: boolean
    & H; S1 S4 }2 V& ]- E8 o& E' _6 A3 l$ ~9 A8 N
    1
    : g% B# N3 f; R, N$ v  f( v: u% Y2* }  f% u, _8 N4 }
    3' r- z2 V8 E+ j6 a8 j& E  H: V
    43 W8 H4 O+ m* s3 g9 ~
    5
    5 _5 `4 z) Z3 j# O. u69 g+ N3 ~6 ~' M0 V6 ]
    7& P* H! n$ D  G. f
    8* x0 G. R, w. Q6 s7 {" \5 x
    9
    4 L  ?, |( a: \5 [, w( K1 E10
    * Q% \# e, N5 f4 r; f6 _4 t112 c, o/ b9 q- b8 [. Z; ]
    12
    7 r0 |! @. N/ P1 x% @" G13
    - ?7 K" c, W5 o7 l! i14
    ! }9 e  o- z, O) g! K. I15. Y5 g* B* m6 N, n, m. T* w
    16
    5 S8 ]- I1 Y5 W% \3 w9 Z" H' e$ S$ |9 T17/ [' Y- }1 C) G1 H6 j/ Q$ p+ N
    18! a8 Z$ J8 p/ w# p; }
    19
    + e: }% v+ }" k/ x/ X8 c20
    $ S2 w$ `8 X- c9 t, `+ q21, A/ |1 o: d% n  W; ~5 r
    22
    # P( m7 [: t' J0 p3 \23* l5 d  \' c4 s6 e) O; e: z
    24
    / Y- v( \5 k9 ], D# P6 w* p5 y" X3 a25
    " I: d4 P; m6 ?( h263 _9 \% K$ y1 V- `1 c
    27# c* c/ w, R0 x# E1 K
    28
    ( u" n; T0 T' P8 \2 t4 N6 _# w29- x- N4 F# K- a
    309 E, `' v/ f& L& ^
    31- }2 u$ w. Y$ P+ X
    32
    . W! W* |) d. n) M+ {% m6 }33
    0 Y0 U4 m) V9 ?3 H34
    3 O$ J% x" c  C35: i, E5 g. O. d/ J* t2 C8 q
    36% }8 h+ l5 ~& |! U2 P
    37
    ' K& X7 n0 N: E; I38' N& k! P, c1 p* y& w5 [
    39
    # |4 M9 h# E( H" a" Q40
    ) j$ r, T  h: s/ o8 [& M41/ a, E$ @- C3 }, U* A
    424 j/ `6 [8 y7 P
    43
    * r. C( f' l7 K- [44
    2 F* L5 S1 D9 B# Z7 \( `45% D8 f7 }1 o3 O6 o! l  p
    46
    - `5 m+ }6 w+ J/ |* b9 E47
    * O' {- ]  N$ C, I/ `* ^  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :; S2 `$ P9 i' G/ O7 o) z& M

    . _8 T7 @+ b: T2 Cs = pd.Series([12, 345, 6789])4 _: Y8 U, C. \( f
    ! K1 L: i" F4 `" J; P4 M
    s.astype('string').str[1]; n7 T- i* M* m; g
    Out[27]:
    7 w$ I  a$ U- }8 e: w; J* t) h0    2) n! K# q1 M, N  Q
    1    4
    1 e$ b4 @9 S0 ^' ]% _* T/ N7 C6 z2    7' \9 n1 _3 _  z% r& H( `
    dtype: string7 v& N3 \& a0 ?  j% y
    14 ^8 v. p' P7 b: [" D0 r* G
    2
    " {/ @5 n( _5 X/ ?% ^0 e+ Q3
    4 d. [! Y4 e+ f9 Q, h4 M0 }( F' ?4$ ]# p2 C1 i8 t, _5 }6 v; l5 j+ f
    55 e, O' U: N+ u
    6
    1 V' K* B# N5 B2 J; |( U7' {2 w1 f! I2 E8 J
    8
    % x3 b9 c+ L. O8.2 正则表达式基础
    - b6 d8 y9 n; J3 N/ i1 i& [* ^这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书8 s! @4 P& f, `. f

      D' i- @" }. ^; Q; @8.2.1 . 一般字符的匹配3 a$ Q, @. s6 |* n: t
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    ' c- [3 N# h1 h# G* h: F: _8 L% A: q, ~+ ?5 i: \
    import re7 a1 V# D) {( v& f

    0 B: ~% M+ u! X; V9 `re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配- }! i2 ^. O4 E
    Out[29]: ['Apple', 'Apple']8 \3 V9 Z1 e+ q9 a  Y  t/ s
    1
    # J! M2 ^( x# R21 [1 e) L1 s1 g; a
    3
    ) R6 s# G+ T# X6 S4
    8 a% x( V  \! c4 `; n7 t) j8.2.2 元字符基础
    ; V6 W% l% i5 j4 p! m9 g元字符        描述
    * ~4 m# P9 [0 ~2 m. s/ O# [: @.        匹配除换行符以外的任意字符! k9 ?6 v4 w9 v8 S, B6 w) W. x6 _+ X, v
    [ ]        字符类,匹配方括号中包含的任意字符1 w: q( }- _8 I
    [^ ]        否定字符类,匹配方括号中不包含的任意字符# g: M3 D5 `/ n
    *        匹配前面的子表达式零次或多次2 p# r. \6 q3 b* o& B. D% B! V. X
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    * g1 M: N1 j* ?! x?        匹配前面的子表达式零次或一次,非贪婪方式
    3 D9 m; x8 v9 Q! w! x" E. E{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    - g4 b/ n) V3 \9 z% y3 _$ F/ g(xyz)        字符组,按照确切的顺序匹配字符xyz8 e% s; k' \. H# ^- W9 m# w
    |        分支结构,匹配符号之前的字符或后面的字符: t2 l8 P  I5 ~% ^
    \        转义符,它可以还原元字符原来的含义
    7 B, |- ~7 g# \( w; Y. U8 s^        匹配行的开始. d' [) S: x* M3 Z( h" \* A# q
    $        匹配行的结束
    " ~9 ?* T- w: d8 L6 {import re
    5 Q! Q- w+ \+ N; M, D" G6 Wre.findall(r'.', 'abc')
    9 z# K+ s! x* ?& n" oOut[30]: ['a', 'b', 'c']
    - B$ d# J) F- ]; A% i5 _1 j5 |- C7 d$ S
    $ L  \) B* s) `. Vre.findall(r'[ac]', 'abc') # []中有的子串都匹配/ {$ m' q8 d9 |5 r0 Q& l& l
    Out[31]: ['a', 'c']1 P/ t- ^7 ], W. J  S  C

    1 v+ i3 i/ T" {1 D3 g2 i+ b1 \5 @, S/ ?re.findall(r'[^ac]', 'abc') & {. ]" v) @6 c4 h
    Out[32]: ['b']
    % u: E; H' T6 [! D* H5 M) i! \4 ]: F2 m; O; L
    re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    0 x3 r; q5 i  l5 ]0 cOut[33]: ['aa', 'aa', 'bb', 'bb']
    " G! m. h; L$ I: k3 z
    4 ^$ ^' K2 W8 qre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    6 F) q8 e$ E/ R' l4 c) \; UOut[34]: ['ca', 'bbc', 'bbc']
    0 e* {+ c- u! F; D3 S" L  G# ^
    7 \+ s; e0 N% n( I9 ^" Z7 F# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    " V' l% G4 o8 J+ F# w, E( M% r5 b"""  k  x+ w, b+ D9 F( `: \; E
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。) F# I1 \& j# V6 A2 v
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
    ' w) c5 f; e8 n; p4 X: Y2 Q9 e3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
    * S# l# F* @4 U+ G但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    7 N- Y: Y0 @! d6 M6 a' g"""& I! r8 B) Q- r/ o+ n% e# G
    ( g! n0 _8 S0 l3 C# r0 J
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。8 f' q- ]4 @  O" _5 y0 @
    Out[35]: ['a', 'a', 'a', 'a']
    9 k8 D( r" X  c- f5 S
    # I5 U1 g# j% s# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。2 }8 d: W. X' [, \0 ~7 Q) X! L" b
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    " @& R+ ~  a0 Q$ N: r! Mre.findall(r'\\', 'aa\a*a')   q) G# [8 R/ b" O7 R, i
    []
    9 G) O. T$ J& S
    8 ?' t& a3 U+ c+ Rre.findall(r'a?.', 'abaacadaae')
    6 @8 h5 h& y/ x4 ^" mOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    $ O6 h3 g+ j4 s+ A# d% X7 _0 x  n" y! ~/ P) w' \* y
    re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    7 a# ?9 ^/ A" S* K, f9 I[('width', '20'), ('height', '10')]# X# N1 W; z# D& f; h

    ! z. Y  P, h2 ]% |  J4 z1
    # f. A! [8 k0 G7 n2
    8 c7 x1 r2 I( K/ i' t36 R1 v1 R- k/ [3 M" m
    4
    ' @( i& s! p6 a9 @" @+ P5  F9 L' r. j' W! f
    69 m, j3 U  V- ]7 u
    7
    + g9 n, `4 I, b0 D" p' {; L* Q: N. @8
    1 M# x0 k6 x2 \& ?9
      Z1 e/ F% |, C3 N10' G! m, H8 }2 R2 ?, q9 @
    11
    ; ~. R  ]) t$ Q8 v- t12
    : k9 R% C6 a8 U5 Q% r' y13
    ) x( G( L* k5 Z3 }+ `14! W. ~- i; I3 _4 U* J: h
    15
    - U% p- R! w5 m4 g1 Y8 L7 Q% \5 v16
    # _8 y/ K5 l( `: f9 S171 O1 ~' C/ p. d. E
    18
      s# K5 [' Q3 `# n195 _0 X% O) v7 X* C  ?6 e
    20
    / i0 `3 w! x7 O  l* u21# y7 B) z* R: n$ w$ i
    22" [; L" g1 a7 m7 D. O9 L3 V
    23+ r; c+ I: N1 Q2 T. l1 q2 a  P
    246 `2 A5 E( H2 K: u2 u' }# P" h5 C
    25
    * H- R; n3 u5 Q3 N0 k6 w26/ B  u7 g$ V, |4 M- n! Q
    27+ S0 |; a1 e& I% ^5 N
    28
      e5 ~1 P2 V8 a6 @" U299 f4 }4 R7 ^6 F/ R" Q1 A
    30
    ( N2 I1 b+ b& [; S& ^31
    - x, Q$ O7 V7 K6 F! r% s* |32, r! v" c: \0 U( ^. o- w% q7 t
    33
    ' H5 k! P9 K+ i  s6 U/ I7 I34/ H: C; B& r/ m2 D& C
    35
    + ?3 g5 E3 Y/ i1 J) E360 ~0 s5 z2 D# k3 z0 E
    37+ k' t4 m& j3 `. C1 ]7 P
    8.2.3 简写字符集
    & m: m; ~( J; |! Q! ~) g则表达式中还有一类简写字符集,其等价于一组字符的集合:& r6 R9 V4 n- v) t" H
    " w* B/ D+ G0 R: u
    简写        描述# [$ D! r" x( D+ _- x5 r3 L
    \w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    / R2 q8 |. n% v) p, I3 `0 m9 n\W        匹配非字母和数字的字符: [^\w]3 g1 x" h) I" n1 i
    \d        匹配数字: [0-9]
    ; |' ^0 l- U  |" R, E\D        匹配非数字: [^\d]: R3 e. C4 H! P* K# J' u
    \s        匹配空格符: [\t\n\f\r\p{Z}]
    , Q3 ], G+ Z4 \1 I, M+ X  C\S        匹配非空格符: [^\s]5 J' w+ S6 b) z. F* i
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    * e) N4 F6 _, y# p" b$ ?re.findall(r'.s', 'Apple! This Is an Apple!')# v3 [; M4 V9 H4 n! g
    Out[37]: ['is', 'Is']
    2 k9 L( r! q6 D) F1 N( t2 Z0 x2 Z1 Z  T
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次' x9 u$ h) p! H9 X5 Z
    Out[38]: ['09', '7w', 'c_', '9q']* R! S  @3 n1 O/ r6 F
    " J$ t' v  x, ?  k9 ?, \
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    8 Q$ y- A: O" Q" N0 TOut[39]: ['8?', 'p@']7 y; B6 k, X  ^, I; t* l) k! \

    % N& h* \7 R" }5 Kre.findall(r'.\s.', 'Constant dropping wears the stone.')
    : [0 F- @; X( ?9 X0 [, QOut[40]: ['t d', 'g w', 's t', 'e s']6 }7 X! t2 m) F# y4 v( O, G
    9 K& a+ K. h2 O  b8 b
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
    " J) B) U( [# D           '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')5 v1 v/ V0 L# G; U; k

    + G- H8 g% e9 i- aOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    : x: X, X, }: s8 D; p8 B
    , H3 J/ N6 a& ^. M17 t- w2 J: a6 m- |0 |
    2- b4 }8 c9 d& R  Y8 z
    3% P( M" ?/ Y9 X$ _  D0 b; k/ d
    4
    8 P7 J) \2 l9 K9 N5' o% A& \& c, S  {7 |
    6
    # s+ I1 l0 u. q' \6 K+ }7
    , v- O) m" r4 Z) u8
    : R/ Y. B0 ?0 h9 p+ g4 G- j* x9
    * v" w# g. B$ A0 _* A& U10
    / Z; @8 c% x/ R$ w. r; s. [11
    7 }7 Y3 k  S# |; U8 {9 r% G12" x' g' I& s0 T. [' w5 W
    13
    + q+ c( z; l" `6 S14
    4 q9 g  N- C% B% F: ^15
    2 n8 y+ N- Z3 f7 i0 x! E3 b$ O16
    . n0 S9 u6 h" c" k+ C8.3 文本处理的五类操作. L) |3 V  ~# \' Q9 R
    8.3.1 str.split 拆分
    ' L2 k" A' L" @2 ~: I  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。% V, V* T, [. F7 P7 ^& }
    & W6 B* @9 T* R' g2 w, p5 q5 h
    s = pd.Series(['上海市黄浦区方浜中路249号',) r1 m$ q, e$ ~
                '上海市宝山区密山路5号']): w$ o1 I& A& C. H& X

    + d* q* Z" ~3 A( R( M4 B+ q  \6 E1 h! {# ]3 f
    s.str.split('[市区路]') # 每条结果为一行,相当于Series
    ' g- i- V5 A$ `( L$ W$ Q; yOut[43]:
    # E, d) C: g+ y0    [上海, 黄浦, 方浜中, 249号]
    . L( X- V7 g. i! b4 u/ |1       [上海, 宝山, 密山, 5号]
    # x8 i- |5 K! t) N" a) R: qdtype: object
    ) r7 C) ]& Z; ]3 V
    & f7 u* _. n0 q* u, r& v0 ]2 js.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    2 D: _; w& ~; _% KOut[44]: : w0 u9 b1 v& e& m+ N
        0   1         2, c6 T$ O( \5 }7 j1 h7 n. w
    0  上海  黄浦  方浜中路249号* `2 }  C1 u1 K% n
    1  上海  宝山     密山路5号
    % w" E8 [# t! J3 _+ g- a/ T1 t9 o1
    8 v' N" }. s/ E- V) N7 z' T2% w7 v+ W7 h) ?
    3, ?% y: R4 c$ c" ]. w* q
    4
    3 t9 C( A5 k( t  Q; M  F2 L5, o* i- C! @& d4 Z- Z
    67 u6 v8 H  O- A  z
    7
    - c. S* S! Y( H0 x9 F- d5 o$ u8
    " g! E' o; d- n3 S, W# E1 b9
    4 N% U8 V; A& U+ I, O10
    ; q8 Q3 z  W, u% t: s8 p2 x. J11
    ' j! f) U0 c1 i; T% B12
    2 h, d: A- _1 t; |13" ]  a4 |, ^' j5 A# p
    148 k: S, h5 e9 J1 T$ W) Z
    15' U; A$ M' p4 `2 g
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
    / E% L9 w, e: u6 T% K( I1 q/ S5 }, ]6 O5 N6 r2 ~4 v7 z5 k
    s.str.rsplit('[市区路]', n=2, expand=True)/ y0 [: M9 L7 |. {2 c) p: u! I
    Out[45]:
    2 l0 ?' L+ z' V) r& }                0
    8 h# @; D& b9 D. H5 H1 ]0  上海市黄浦区方浜中路249号8 F/ U' A. T. {* j
    1     上海市宝山区密山路5号, H0 d2 _# z; B8 Y
    1
      f1 [5 G5 a" @24 u9 B% h. {1 h0 {
    3
    2 x. @  L% i/ F. B- i4
    ! m* G, N! q2 z( p+ I50 i2 j& w6 h8 d* l4 x) H
    8.3.2 str.join 或 str.cat 合并
      r5 a7 B0 p% R9 x6 ~str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    . C/ m7 w8 \$ O; n4 P3 X6 U5 }str.cat 用于合并两个序列,主要参数为:5 F" n& X" r, Z0 v# k/ g8 w4 p  g
    sep:连接符、
    7 x, D" u. t& }3 ?1 f' {) M0 [join:连接形式默认为以索引为键的左连接: M& b3 Q& J/ Z+ w( w6 e: |
    na_rep:缺失值替代符号6 J3 i+ Z3 H! @4 H) d5 ~
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']]): W- [5 a$ b$ X
    s.str.join('-')* }, e; F  w) A: _8 V# w, q" p0 m4 o( f
    Out[47]:
    & Z6 J7 Y$ E" ]+ U) p0    a-b: D9 h2 w+ `; U. x8 m
    1    NaN( l; B/ Q: p+ N
    2    NaN
      ~( g+ t6 g0 q# Y4 V# d% F$ G; m  \dtype: object2 X- s. X9 C4 C/ M
    14 S, n7 c: T0 A; F
    2" y0 u) Q  m4 J
    31 W" S5 b$ ?! h
    42 H& c+ _3 t' s- i: d( W
    5
      J: ]5 W) C+ C5 V; n3 V$ ]6
    & u- m# }3 w9 U$ }; o79 B8 y+ B, q1 o4 Z
    s1 = pd.Series(['a','b'])* g# }8 d% a0 F/ ]8 m# ^
    s2 = pd.Series(['cat','dog'])! `* j) ^( k! z
    s1.str.cat(s2,sep='-')
    : Z8 A9 O4 Y: o% ], Q# z+ bOut[50]: * l% h1 l) p( c
    0    a-cat% P& \: U5 Q; G6 K% o# j
    1    b-dog
    % F4 q+ }# |- d7 ydtype: object' Z/ b, I4 P( W9 \
    $ W8 Q" w' M' {
    s2.index = [1, 2]  n3 V/ M8 s: j0 X+ E
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')
    . l) h. V' W! pOut[52]:
    ( I% r& w0 S" u( A0      a-?
    ( n2 D( ?' R5 D" G6 X1    b-cat
    + M0 ?) I3 ]2 T2    ?-dog0 N# X; P) ^6 I0 O. n- g
    dtype: object, W/ g9 d4 k, I3 z
    1
    ' N  [6 ?' f0 e: @2: q+ g5 x8 U) [( M. D
    3
    # Q% i. z1 ?  o# k/ x( M+ A4 h! C7 N4) W8 T/ X- j$ Z: J
    54 S% f( R/ Z/ Z1 n. {. B1 z
    67 \& V+ q$ p1 Y$ u& h, Z" u4 Q2 }/ M
    7  {& a( u9 q& p% S7 P
    8
    4 B) Y5 c6 ~  ^0 W- V1 Y4 R9
    ) }& v) L, o: |; d10
    2 J1 h( y  b! R. N# {# a1 L11  D: f% ~( G1 f
    12
      D& h1 f: \# J5 x* \( N0 ^2 o( ]13
    % l2 z% u: E) G/ \  ]2 n! }1 M140 U* V; n/ S: @( j' w
    15
    # C8 y( u7 K/ G2 N& w8.3.3 匹配
    ; f9 g) }; R3 Z8 H, dstr.contains返回了每个字符串是否包含正则模式的布尔序列:
    4 L; Q- {: B/ z* V9 I) l4 `* C$ Ls = pd.Series(['my cat', 'he is fat', 'railway station'])) t& T$ X. z" ~! l0 f: @0 h8 R3 {
    s.str.contains('\s\wat'); J! J4 R: }* M  \& Z

    - i6 I! m' r& Q4 J+ c) M6 g& c+ C6 x0     True
    + R6 |) W* ~& [3 w' E! Q0 f6 g1     True
    2 u2 P8 C7 G; |" D, @2    False) c: E* X8 @, J* |- @! y
    dtype: bool0 e0 |4 r9 z6 _: T
    1
    & I+ _/ @( H7 e1 B5 t! z) q2' k, i) r6 x. i6 W7 D
    3
    5 \$ h; V, g0 x* Y, P* }6 s4
    , y& c7 ]- Y0 {/ P5
    ) X! e- I+ Y7 N" m8 ^6
    . R; \7 _  L3 r. y0 T3 \" N9 d- x4 K7& s) r7 l3 Z  x0 a
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:! O5 l9 R$ R# t4 W" B7 l* ?' N# _
    s.str.startswith('my')
    ! U: A6 c. Q. w8 i. @3 w. Z0 g3 H9 j1 o% p( h
    0     True
    , U1 c& x  q( c/ {2 u7 B1    False
    7 R6 h2 V$ B& X5 `: D2    False
    ) b9 J) @0 f& odtype: bool1 E1 ^, t9 Z$ ^% g) h& i$ r
    1  r9 h: W% Y1 F) q6 x  x  w
    2  w' c8 H- ^- y$ K
    3
    / V% g. M$ X: s4' ~( C8 s( m0 W
    5
    : a6 G- O6 n) m" ^1 Y6* o; I) W8 Z! h9 C. J- U# ^$ F
    s.str.endswith('t')0 j- O3 [& V* t7 `/ v' Y. K
    + x# ]7 m) H3 e1 {. q
    0     True, Q# ~+ {* x; `- Y7 E
    1     True
    % ~% ^3 P4 k) R, ?& ?7 u2    False2 \9 s8 D& S  K2 O4 Z
    dtype: bool! C/ I4 e* V! r( m7 z
    1
    2 I3 e; g$ d: h$ l* k8 j2
      x* y3 r4 W/ @0 h. v- E3
    0 G& b9 e$ ^  F  X4
    ( O  K6 C$ C4 W3 n8 Q$ R/ G* `8 C5
    , X0 P9 l) s3 r+ q6- ]% v4 r, }  O
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)" _) K  M. e. X7 |
    s.str.match('m|h')! w* m6 Q/ t) x9 V( t: A! q' ?
    s.str.contains('^[m|h]') # 二者等价
    / m+ Q$ A9 u. ]- X4 L/ b
    , I& m6 @* q" L0     True
    & C  Y% l+ j3 @1     True
    9 e$ F/ }2 a7 u/ y/ O0 E2    False
    5 ]' c4 n" R' }dtype: bool$ @* s, c0 R, {) u
    1
    " t! w  J- E$ J0 ^1 R- n" {2" P1 {/ r0 E) r9 W# [: c, O9 n
    3
    8 Q$ S8 M# |) ]( ~40 [4 [# Y$ X. P% P2 ~
    5
    ; J( p6 ?; x! z- [" p' i6
    ' A, q4 ]8 s: Y8 Q% J: P+ N7; Y$ A4 N! X) b+ |0 e, c
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
    2 D( R4 t+ J. S3 L$ gs.str.contains('[f|g]at|n$')       # 二者等价+ e' P9 w! l6 n4 B' O( J5 z
    8 X0 W0 B4 ~% j% T
    0    False
    1 m5 ~& u  i7 e0 t9 a1     True  u4 P; x# `* d1 G0 b! }
    2     True8 W2 L! M# N* Y
    dtype: bool
    ; A2 B1 @6 w% X7 I- C1
    1 X- q: c" @# r8 E7 t( N24 n( K6 |8 X9 u. ~0 f4 K( I8 q
    3
    8 `9 C: |) x  x+ H  G5 o4
      [# X; n- j1 n3 |2 v- B  g5
    6 q& @, g5 f: U. W, c6
    ; l1 M% U9 J) a! m7
    5 Q7 r$ ]+ R6 m! T' N1 ?str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:- O7 I/ n2 K. }# D
    s = pd.Series(['This is an apple. That is not an apple.'])
    ( m# B* h. r1 h$ O0 y+ x! @$ u! g7 V2 @6 r
    s.str.find('apple')
    " f/ m, p5 c4 \5 f, v" H9 W- TOut[62]: $ ]4 Q  z7 N# L4 r0 X
    0    11
      u: R' ?7 ?6 J" P) ?1 N* T& J& b1 O4 [dtype: int64
    6 B9 m: y4 u' m8 [7 c
    3 z! h! o) _# es.str.rfind('apple')
    # p4 F4 f7 ?- h# A4 |$ ^  AOut[63]: ; ]4 F* r5 l+ E4 z. B
    0    33
    ' A& b' D* x& o' @# vdtype: int64
    0 ^( v/ u) ^2 O/ W# E1
    5 x5 Y) J7 l+ Q* \; k$ ]$ V2
    % E0 K" z% B& o- U3 r" M4 f: B3
    # s* Z% Q, o# X46 R1 A* H8 g: n) ?5 B
    5  F# w; o  L: s( u$ H1 v
    68 Y. T& ~% B) M/ _) x: m3 G# C
    79 K  h0 C" i) N: K. D: X
    81 a( y& T3 R; R: d/ Y) b+ d
    9
    8 w" r9 s6 b! b10
    ' y5 w. U' _" O11: u  L$ ?6 }9 I# t5 o
    替换1 v) x6 A% c. d) [6 Q3 o
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。6 U) V: L* ~; _  U' `& @
    s = pd.Series(['a_1_b','c_?']). p. {4 N; H. F7 ~7 I* o
    # regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?- w4 P, C& \  I) L. K. i
    s.str.replace('\d|\?', 'new', regex=True)
    % G0 @4 U% v; b5 P; a. H2 G$ L; [3 x
    0    a_new_b8 [( Q8 q/ T4 _' G& d/ a
    1      c_new
    + w. e& w) [1 \) s+ xdtype: object
    ! v5 J, k" }  w/ n1
    " z9 M1 M6 V( O! v28 F8 _" P$ b9 \
    3
    3 o- }9 H3 E5 Z+ x5 E* X$ Q- d4, b, S1 B: t* L
    5! M9 K7 b8 J% F# C' w% j3 W( o
    6
    ' C# w* q1 x. e78 p8 G9 J& a$ Q: w" p
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    % S8 Z. _) y. T1 _5 a  j: j: e$ w- `  ?' G
    s = pd.Series(['上海市黄浦区方浜中路249号',
    ; D7 a' ^4 ^% [3 V" c" L, t                '上海市宝山区密山路5号',: n+ U+ D( E) [* k1 c
                    '北京市昌平区北农路2号'])! g; ^6 E; d$ b8 F6 H3 ~- K
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    * G+ q# @8 u+ L# I3 j1 g+ B- n+ U. _city = {'上海市': 'Shanghai', '北京市': 'Beijing'}0 @, V$ G+ e( _7 q
    district = {'昌平区': 'CP District',1 Z) `$ ?! u2 k+ O# @5 L# _, ?
                '黄浦区': 'HP District',/ v( Y/ r- y7 `5 P. }
                '宝山区': 'BS District'}% A! U" a" V/ c4 Q* @! q
    road = {'方浜中路': 'Mid Fangbin Road',$ v- a! B, q' Z$ V% K/ Y% ~& [
            '密山路': 'Mishan Road',
    ' j( `. p( N; c  I5 r' Y4 T  A        '北农路': 'Beinong Road'}
    2 n) O% i/ a' |2 _def my_func(m):
    - M( A4 I( l8 L6 l1 Y* r5 P    str_city = city[m.group(1)]
    3 o  u( |* m$ w6 G/ s* ]6 Y    str_district = district[m.group(2)]
    " T& ?9 P* G' [    str_road = road[m.group(3)]5 l5 s7 ^, @; T- ~: U4 q
        str_no = 'No. ' + m.group(4)[:-1]
    % B7 _+ E; g/ a6 X    return ' '.join([str_city,
    ' M1 L, {. F2 _1 h# L* |5 r                     str_district,
    $ D! D& m7 }  K/ M9 i# C                     str_road,+ q0 J$ [/ i. x/ t0 \; o/ O7 Z7 Z* r
                         str_no])
    7 e7 i) _" `+ s4 _s.str.replace(pat, my_func, regex=True)
    $ G5 [( S0 ]) e7 u3 s8 x) t5 x4 B  L+ ~" E% m; j% a0 f, r" }2 N
    18 f4 _6 x4 [0 Z# [
    2
    # r& J) \' W; K$ [0 p2 r, I5 H& G34 {6 F" q& f( o+ ]( J- m
    4# n: b8 h$ c  p$ N4 I/ U
    5% u% f0 D4 L! @' a9 {; s6 y* \
    6
    ) [0 j' u8 D; W. \7 e- b7" b+ S. ]) O# W
    8
    6 {% s2 Y* E& o4 S; G$ ^9
    / Y. D6 f, W; ]. t2 `0 J6 r, y105 n, M- @8 b9 |, o: P
    11
    9 h9 G3 V! ~+ P& g) E6 F12
    " P  p/ a5 f& T: z, x3 C; g' c13& e2 j4 f0 _  x* h6 [! W+ h' ~4 n
    14
    , g  T3 @4 |- V6 j$ ^/ {15  x5 U9 l2 Z, ]6 W
    16, g$ U  t( g! \) z2 U6 @3 G2 _
    17
    & ^* j$ g  u3 q( d! R180 m. T1 |& n2 l
    197 Z9 |) u0 R+ F: z
    20
    $ H6 l. [& C; ]3 P21
    : n& E* Z( c, f4 j& `3 i; P# y# w0    Shanghai HP District Mid Fangbin Road No. 249; ^+ E! ]' P0 |% _, ~$ S& `9 ?, ?9 N. `
    1           Shanghai BS District Mishan Road No. 5
    8 d5 x6 c; E& C! C. J4 J) W+ _- c- ^2           Beijing CP District Beinong Road No. 2: c. E+ s$ e  D6 Q7 O0 Y2 a
    dtype: object' J4 ~' a5 _/ a& r; {1 c+ O$ H
    1. b) H& |( a% v: D4 F
    28 `3 h" d) u, z! i
    3- q6 h6 W4 j: S6 V
    46 @! C' r9 j8 t! |
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:# D& V& j. A! v0 Q8 u) Z

    ; h$ x9 p7 R/ `. g; I# 将各个子组进行命名1 E  U( k- Z) Q7 R7 O4 y
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    3 @) n8 y  L/ R- `% h5 N( Ldef my_func(m):
    : H& A% ]$ g4 o9 g+ n    str_city = city[m.group('市名')], \* N( F3 ]' N+ h( W( s
        str_district = district[m.group('区名')]
    8 o2 W" e! P  m- \* m; Q    str_road = road[m.group('路名')]
    8 s4 z) t, m3 I    str_no = 'No. ' + m.group('编号')[:-1]# G+ _* x* C: T5 F
        return ' '.join([str_city,8 k$ h4 ?' S! u  Y) H
                         str_district,
    + h8 E2 V# o  R                     str_road,6 M: F5 o9 S) [$ }# \
                         str_no])
    4 L/ Q. U& g8 v8 y0 fs.str.replace(pat, my_func, regex=True)/ {0 D9 @, P5 f1 N
    16 n6 p5 {8 O" q& e6 ?3 i
    2
    $ W- q9 z0 ~: t. b2 \3 p3+ B; @$ o( r* f+ ^. e) _" `; S
    4& X& r2 m9 t# V% g2 b
    5
    2 }+ q9 p# E7 a" o4 V5 L6% g+ U0 s1 I3 {4 h
    7
    8 {% v) K/ I# Q1 K4 O5 }, G/ T8
    9 r- f" ~; m, |9
    % G4 d- f# i6 ^: ?! ^10
    2 x9 H4 g# s8 V5 H& A/ K( X8 d. K11
    ) \8 e2 }8 p( O( R& p2 k8 g12
    - F7 c" @/ I% i9 P0 b1 w" b0    Shanghai HP District Mid Fangbin Road No. 249
    ; [0 M4 {8 @8 s4 S0 C1           Shanghai BS District Mishan Road No. 5! z/ R, x. z9 s/ U6 |
    2           Beijing CP District Beinong Road No. 2$ p. V$ Y: b# i9 n3 |& f: Y
    dtype: object
    ! v3 t. X: I5 [: `+ C1" x4 p" C( x5 H- a
    2
    6 t7 ]4 j' b4 _" V3
      A6 t4 A4 C% u! A% ^! P3 r! I  H4
    - h& s# I; c' P) V" W  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
    , v% |4 T$ W: ]4 I! O' X; o2 u1 o% S+ J1 X
    8.3.5 提取6 o0 q; O$ z9 L7 M. w
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    + U) j& T6 q. P+ j5 w% S$ Ws.str.split('[市区路]')
    + T5 n4 o9 B0 h! ~Out[43]: 7 a- D# L# }' ^) t. F
    0    [上海, 黄浦, 方浜中, 249号]
    . r" \( Z) R  h! L5 }% g1       [上海, 宝山, 密山, 5号]! P- l  R( V! |  h8 p; r
    dtype: object, m) z& b" ^, n, f- o

    8 a& }- }% J0 {$ I6 h6 N) {pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    ) i, r# j, P; ^4 ys.str.extract(pat)
    ! h( {7 k9 B' Z7 A" bOut[78]:) r6 _* D! |  M8 _4 Q2 b; u" y6 ?
        0    1     2     3; N  W- z; P$ _$ U' r
    0  上海市  黄浦区  方浜中路  249号: r, @2 G; @8 A* d5 D2 d
    1  上海市  宝山区   密山路    5号* c6 M0 ?" U7 X, H* x
    2  北京市  昌平区   北农路    2号7 L- ^' q* \0 b. @
    1
    8 z5 G# ?6 c% y7 D" [' z2
    % R  g; F& t# \5 b+ k! [3
    ( P& r5 l- `; i2 v8 Z4
    . y% \# b6 H& O: J52 z+ w9 U) @9 `; a( L+ B3 w
    6
    3 y. h+ z, Y) `! P( x- W7
    6 ~. t$ P. e# O5 z/ F; d/ a6 U8
    , r3 r4 S0 z5 w: B: |8 }* X9
    " E. i) r/ g9 J8 m0 F7 x( R10
    ; ^3 A0 U. H' a& m11
      h: A6 [1 t. ?: x8 ^9 u12
      I1 P& G+ o  `; [6 O  q13% ?2 P. V- d) c7 f- u9 x
    通过子组的命名,可以直接对新生成DataFrame的列命名:
    0 d# t; W8 s% b
    $ j7 v; d$ `4 c- W  F( ~pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    5 E" u( }* v, o! Ws.str.extract(pat)
    ' M& U. I* ~2 m2 E  QOut[79]:
    ) u& [* a7 z+ H+ ?* Y: u# `8 t: A+ I    市名   区名    路名    编号. f7 E% c% C/ b. ]8 G4 k8 L
    0  上海市  黄浦区  方浜中路  249号
    4 x$ I$ q- ~4 n2 Z( T6 Q1  上海市  宝山区   密山路    5号# A/ j& ~3 D. b9 @
    2  北京市  昌平区   北农路    2号* d: {* p& E9 N3 V+ @
    1
    ! o+ }8 i1 G, I0 D2
    7 N# j' _$ d) l+ x3
    ( d" X  Z6 T2 P2 r46 b$ P3 x/ H- F- w) Y7 {+ Q" P
    57 B" E( ?, Q4 q$ p
    66 g3 x  e) b9 P0 R" P7 d
    79 E5 y1 Y) j/ G
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:. e4 Z8 v$ |6 m# A: w5 G# ^4 Y; I* D
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B']); W$ Z6 }& V- r5 M
    pat = '[A|B](\d+)[T|S](\d+)'- f) L& c, w4 b) t( ?0 {7 ~0 P
    s.str.extractall(pat)
    & l' z* p9 Z2 Q  ]Out[83]:
    6 Z" T& g. V* H       0   14 l3 t' K! @! i" H) p6 J: c
         match         & q5 U8 _. m. x$ _& h
    my_A 0      135  15! W( T+ b# K8 T: n' |9 a
         1       26   5
    . G5 e# d: f" T/ jmy_B 0      674   2
    ) L( g$ @8 v" M+ C5 j6 G* U     1       25   6' g* r: A" g6 b( x, O2 k
    16 O: O4 f/ V& ]: d  i9 C0 J
    24 j, z6 ?/ B. U0 J% o
    3+ |! x! u, R( V! M+ g8 s
    4' W" r# a8 B# E0 V
    5: K6 X+ F9 x- N: d6 ?5 @
    6: H: N& J4 a% I# N) A$ L2 s
    7
    5 J5 ^1 j$ Q1 l2 Q, Q+ [+ f3 ]8$ s0 c4 |1 K7 {+ G. s
    95 _& U- c0 b) B3 T0 O: e1 z. d
    10. e0 c# U: g) o5 V
    pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'8 w* s" M/ y' `4 e) Z
    s.str.extractall(pat_with_name)
    - m8 @8 Z. W: X8 h1 f+ N3 ^Out[84]: # V) O& @5 o! c/ ^$ l* N. [
               name1 name2
    ; M; ]* f, y6 t: B9 E$ T     match            ; _. G$ w# \! F; d
    my_A 0       135    15+ s/ _9 F% M. n7 u" S
         1        26     5
    2 r! z2 z; d2 r( {0 v/ c( smy_B 0       674     2
    . B+ X7 W3 O$ ~$ B' R     1        25     6
    $ I0 I3 @5 S# C/ J2 w7 F1# U* |" v0 P  X; z, E+ K
    2
    4 `1 N  o. W& Z3" Z. m, a; ]# U
    4
    / F2 s0 y1 {3 S  k4 e$ U51 ?/ Q' ?; p" g6 d; g
    6
    - G* X; z  V# a+ b* Q2 D+ D7
    - c/ x3 p7 \7 H- I1 m1 |9 V( k2 R( j" ^8
    / e6 @3 p! s: h+ `# ~% W3 M9
    - A# K  h$ W4 r) \7 {* b9 hstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    + ^! S. a1 [  @7 n" Vs.str.findall(pat)
    + U4 K0 m/ V. J- I1
    , u- n  a) q4 A( r3 s2 f+ imy_A    [(135, 15), (26, 5)]* V& D' G7 h6 P! r& @
    my_B     [(674, 2), (25, 6)]
      W: A$ z* z) f1 P' gdtype: object7 W7 Q( \* q3 K- v4 X
    1
    ! f; q6 W, Z$ K: s8 K( O: @2  x2 m" }( u6 ]
    34 t$ |) E+ C7 m: |. C  c5 `( h
    8.4、常用字符串函数/ b3 m0 X% a+ l1 l9 ~
      除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    ' f7 W; x2 r& |
    4 w1 B  W1 g  X! Z) r7 C+ [8.4.1 字母型函数- m3 D- @6 g! z, S; q$ U, g! D; @& J
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    % F/ m6 E: h% H9 m9 j$ X5 a, `+ v2 v% n5 I
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    6 a& |  P5 g4 Z! v) q. I
    8 X8 \/ ]; K" ^- [7 Y! {) N1 Bs.str.upper()  I% J4 m2 r0 q- S  l  S
    Out[87]: / l# x6 o! k8 p0 _* [- Z5 |# k! l
    0                 LOWER
    9 ?6 l) A; J: d( [! |+ J: H1              CAPITALS9 ~7 Z( f9 R5 j! g& h) J% p
    2    THIS IS A SENTENCE
    % }  M2 a! `  w, C: E" v" K5 A3              SWAPCASE" T2 {1 e4 ]8 c
    dtype: object# b% ]. u8 y! l8 ~. I

    2 B1 n5 F6 ^/ [0 q& d' e3 Y$ [; ks.str.lower()
    ; |& Z, m! }7 IOut[88]:
    6 a" ~# n* H9 d: b& n0                 lower
    3 a" T+ G- F3 U  N$ x  x1              capitals
    4 X, |9 N3 e1 i. R2    this is a sentence
    5 R) p% a6 |( {! U+ X3              swapcase
    , M7 x6 @9 }% G, ?dtype: object- Z8 ?' `2 r/ j  O. C( h

    * i) N! O; L4 q( h# h+ u# @8 z+ Qs.str.title()  # 首字母大写
    % L2 N  U0 b6 i! hOut[89]: * C+ n$ L* ~3 m* r4 K& w
    0                 Lower+ d) q) C; K; q( P
    1              Capitals
    3 j. ?" \7 e* K5 _2    This Is A Sentence  x. F: N; O. o9 F
    3              Swapcase7 e4 Y+ X/ ^3 L1 z8 k
    dtype: object
    . ?( @  t) b; s" X! ]. ?/ ~# e4 g. R) D: X& r
    s.str.capitalize()  # 句首大写$ q; }$ J! _3 x. B4 V4 H- ^9 y
    Out[90]:
    ( C% ], L5 W$ {# i0                 Lower: ?2 c- L' S9 H. X  M4 ~
    1              Capitals9 q% Y. U2 M/ [5 x
    2    This is a sentence
    : {4 l' L1 ]7 x* @: {3              Swapcase
    $ Z$ \4 L* {* t; n  S$ c7 Ldtype: object
    & k6 Q. ~$ k# f
    . o& B; K! B5 _; H+ Rs.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    : t0 m( M: n8 e# M- BOut[91]: ( \" [9 z0 I  F" t# n
    0                 LOWER; G. }  M: [- {, A& t) v6 x) i7 B
    1              capitals3 s( ?2 d3 J& g
    2    THIS IS A SENTENCE
    " J% I+ a% u$ z9 P/ G3              sWaPcAsE
    ) J% M& B9 Q3 o# S7 i. ]' a, V' p+ `2 d6 {dtype: object  j& A5 {+ [; p' T# p

    1 ^* Y! y1 l0 O2 ~$ }: D) Qs.str.casefold()  # 去除字符串中所有大小写区别
    ; x0 W+ c$ h* Q9 B( ^, k( u. _; z' E) c
    0                 lower) c; ]0 j! m% ]1 P$ V
    1              capitals  s+ p2 Q$ {, p! f/ t. O! X$ t3 z
    2    this is a sentence* F9 K( L" t, O$ s. @0 E. K' ^( I0 H
    3              swapcase
    ( l. C& c* s! h1 ]7 k8 w; {
    ( v/ C) \7 r$ j0 K; M9 k1
    / H& o6 H/ ^2 C# L6 p2
    , f) }& _) i" `" W" s" c$ Z" F3
    " }) D/ G) u3 X! b4
    , U( S" S/ a" M8 ~7 ~, f4 A5, x' r3 B0 d1 A4 [' p
    6
    7 O9 P1 c( ?6 o- I& B72 T9 V: C; {3 J1 N$ c, |; `9 y
    82 r5 I% f" J6 k$ `) C7 m
    96 Z& }- H) ]! U" o' [1 F$ l
    10
    . A, K, [0 J- ^* i& {1 a( V11
    1 w. X1 K% D0 J, n! b0 P" N4 f12
    , ?; ]3 w7 {; R" `7 x3 c13
    + b; e/ e2 z" i5 O# C+ c7 T/ C14
    , b7 M- ?! \+ f, g' X! b' r, o157 x% P2 Z9 x5 f6 L
    16
    % J9 u! e5 f4 w* e* i7 F17
      }5 J) y: u; _+ @3 Q18
    * B% T9 n8 C/ b3 @* \7 n19$ d$ Z+ w7 z6 ^7 T: r1 W
    204 @5 D$ h1 L3 k) r( Y" O- h! X
    21. `4 x2 l: k: b1 k4 A. U+ ?
    228 f- N( A9 k# F. L8 j
    23
    7 E7 B( S3 }9 {. L24+ l- e" }0 e. R7 F
    254 J6 C. l1 ~. T. D4 X) n
    26
    6 Y: K5 S2 _8 H) B% _- O  V8 u: _27
    , s  W. ?* x, v+ m287 B$ d' D% k( W4 }1 k; `  ~9 q
    29) ]) D) y$ p+ B
    30) R1 ?$ a! e6 f5 U+ X& q! _: P
    31+ O' s1 @3 L7 O- j! O8 F/ r* Q
    32
    : g8 \2 e6 R. w, v4 ^33
    ! Q( C: T; |  Z34
    9 k- ~$ @# ]2 g, y  N" M0 \! v5 D356 g/ j! n% W! }" l
    36) k7 a9 b$ h  N" }! E. V
    378 c7 Q" r# U$ _' H2 \
    38; q0 V0 a$ P8 P7 P! A
    39
    . P% r" F) o( F: l& c40
    - E/ f: ], z3 Y* X# A41. S, V+ Z- c% _1 J
    42+ Z0 l8 z, r+ i( `4 ]7 u7 k4 o
    43
    & ]) N0 p2 o+ R! O44
    % j& S' F0 V( A8 l% R45
    ! k7 ~' ?5 d- r4 [6 ^461 \# _: S; Y) X7 N2 z9 P
    47. G$ x( N+ e# ^
    485 f" ]. x$ k0 f
    8.4.2 数值型函数( ?8 i2 U' Y9 O5 ^2 ]- T
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    ; j& N: S- ]1 |4 Q5 }7 l2 L5 v% n6 {) T  N4 H
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
    - [/ O% P: Q  i) J# ~/ m$ Kraise:直接报错,默认选项( m- \1 C+ r; Z. c
    coerce:设为缺失值8 @9 v$ E. g$ }! |$ ~9 l
    ignore:保持原来的字符串。0 y" l2 [8 D2 L8 u' Z4 w
    downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。! U5 i9 Z# i/ w5 u. T5 q0 L& E7 t
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0']): A0 e% d2 ~7 ~+ f8 ?3 `& R1 ]

    5 u9 Q6 C  H3 K: D2 e: wpd.to_numeric(s, errors='ignore')
    0 @& M4 @' u( ~- L. d) Z: ^# }Out[93]: 3 L3 A* _( ?% [4 _7 V
    0       1
    ) g0 A8 z2 I# t6 j1     2.2* f/ ]5 X2 q. u1 H
    2      2e* z' a2 N( a" y7 F* [  t2 j0 v, |
    3      ??
    % X: V" r: `0 Z+ _4 J5 ~4    -2.1  E( z+ o; ^# Q5 E4 t* B! ]% T
    5       03 I6 w/ U+ Q$ S$ R: M
    dtype: object
    & I: |4 x0 a! B/ ~5 G- i; e3 O8 P" ^8 T  F6 L* p  T; d0 o- Z
    pd.to_numeric(s, errors='coerce'), m3 K& A) N9 t1 }8 O* y
    Out[94]: : K) a- f' y# P5 h
    0    1.0+ k0 s: O: W; s% e) V6 c" J( C
    1    2.2
    1 |6 Z) Q/ i9 K2    NaN* ~+ M+ d6 P) D: {
    3    NaN) B& A1 y8 n$ P
    4   -2.1
    * d) g% l+ U0 @9 a2 X: |3 }, ]5    0.0
    9 O( Y4 s( W& |$ A' b: zdtype: float64
    7 S" U+ T0 Y& c+ m* k6 ?) i7 p  ]! {# k% `% g' p
    16 y( X/ m' d  h5 D$ p' d
    2
    * w  U- G1 O& p$ U( ~36 f. S0 M2 m$ F
    4
    ( o5 m8 F) c' b4 X& J  y; Z5
    6 D* h6 m, \, m* ~8 A7 |2 B6
    8 L" f# e5 s  l$ L: B- ?7
    * _( W& j& y9 p4 [- f8$ T+ g- {* l3 r' u7 n( T  e1 D5 \
    9/ Z; l& m- U4 y$ L+ Q9 ]- w
    10
    $ F  U4 \' Z" D; v; |+ L11
    / D; ^$ X4 v# q7 _+ |+ `  s9 B12: F* N  D8 ?1 Y$ j. a4 @( N
    13
    3 k( u% R& G) }( |) N7 f14
    ) d# Y7 |& L! j' l* K) P9 `156 l" W) A6 G3 M* i5 O0 ^, N) `
    16
    # R/ I( n0 e7 w6 ^2 L; ~17
    ' ]% F1 F6 M5 D; R  {) ~# j( c182 Q) t1 C! F4 R5 Z
    19
    # ]7 P2 C/ X/ F9 I4 r. w20
    0 X; y- Q6 c6 Z2 O& D) p: Q* p21
    ( s' e% H! P; k& n. e- ]  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:& Q7 ^/ i/ R& O& _

    - G6 _; o5 |: c0 j4 v/ J: [s[pd.to_numeric(s, errors='coerce').isna()]
    ( p4 q. b! ~  A! q$ B+ _! WOut[95]: * ?( C7 a& j6 q) E  j; a
    2    2e
    * D: h1 M2 |2 {8 r  k/ J3    ??
    * A2 x- a* Z5 kdtype: object2 {' x7 v2 |$ u  d
    1
    1 [  E6 G, |0 F0 t  Y8 T2
    $ g( s. Y% s4 p& y  p% V6 {5 [  {3
    - i7 F2 q* B! r2 b3 D4
    * S4 `: h+ P4 ^* }8 b  n  h" M: Z7 c5
    . x1 ^$ _/ j1 |: O6 ~- j) B5 B8.4.3 统计型函数
    ; ?0 w( t$ U) j1 H- u( A  count和len的作用分别是返回出现正则模式的次数和字符串的长度:$ H8 M. J8 k- O9 V- T8 z2 N
    6 \4 i" U+ _8 g( @5 r( X
    s = pd.Series(['cat rat fat at', 'get feed sheet heat'])2 d( W. o( ~# M/ e
    0 w8 s+ D+ g2 N! g
    s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次/ e5 ?9 k0 s3 u0 R, @2 ~+ _
    Out[97]: ! r% w! U5 z. P
    0    2
    # P8 G$ ]6 `6 h9 h* T; z1    2) P3 c7 \) e) h' Y2 t$ i
    dtype: int642 Q! O2 V# s$ H8 T- Z

    * w8 O. W) i9 Q+ is.str.len()
    1 T- O& ]- f  L' G8 A/ YOut[98]:
    & {$ H; X* x8 M$ S) r# c3 B0    145 [, l' r: y* W/ [0 q; a
    1    191 N/ J2 w; s! e
    dtype: int642 v  e/ |5 ^1 D0 Y4 e
    1* Y5 n3 `2 d0 C
    2: c4 K6 \3 h: W2 e7 [- Q
    3* w$ g" v( `  T1 w- x. @3 C
    4
    # S( S8 H7 E9 `- B6 b7 B, i& o5
    0 Z- n6 O( o2 h; T& O  F6# g8 b, M. [' k
    7
    7 M$ \* W9 X1 e8
    ) C4 J& ?$ o+ E* H  B! ~4 d97 M! S4 @( E) E( i% i$ V9 m
    10
    & A/ W4 ^8 o6 ]9 A' m2 P11
    & d! f- T+ u& M12; _% F" `, l& S  q- ~1 @( \
    13
    4 n# j4 q% m' A& S8 s) o8.4.4 格式型函数
    & k2 u, u6 S1 h; m$ Q  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
    9 e! p' s2 X. T- W% e
    1 y3 p( _6 T( S2 t0 c! z/ h( jmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
    $ J+ Y7 I5 c* G$ c$ y
    * N, C4 Y" t/ M1 L+ |4 _8 g9 `, d) Wmy_index.str.strip().str.len()
    . i0 M8 {$ l' u1 q  q" `3 YOut[100]: Int64Index([4, 4, 4], dtype='int64')' F1 k) L. a. j' ^" ~' H; W
    8 ^- r/ x5 T- D
    my_index.str.rstrip().str.len()
    2 N2 C$ ^; ~# R- k: F* K4 e7 dOut[101]: Int64Index([5, 4, 5], dtype='int64')7 |1 n6 C4 q. J0 M+ V

    , m* w4 `# h. L  N7 {my_index.str.lstrip().str.len()5 s" p9 N: [' [( g+ ^& x5 \  v
    Out[102]: Int64Index([4, 5, 5], dtype='int64')
    2 e/ p! z1 h$ L8 ?- s, ^( x1* E9 ]* n& r9 H7 y1 J
    2
    ) d8 p- V# D1 _9 ?3
    ) }- `; O5 w, a! `, K$ T' Y4; v- ^  }3 ^' o4 P1 i1 s( g# Y
    5% J5 ~0 D) l3 v4 Q( \5 j* F# ]
    6/ n/ E0 Z- U) S) Y
    7
    + }& O( A/ k* P5 G/ ]0 |  I8
    - X8 B- I9 N7 s4 @! J: x0 }9& w5 q5 K$ t. w' j9 z; }( g
    10
    $ j8 u  c4 b( H' R* a9 I  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    ! H9 d- E9 @9 n  Z5 h, y% H& a$ B7 C, A$ `
    s = pd.Series(['a','b','c'])/ ]1 O( Z! {% E9 D2 ^+ G

      m! r. D0 |1 z" F) K5 p1 [8 Ys.str.pad(5,'left','*')
    ( c1 f1 [# o4 z. y7 J/ N) Q  `- BOut[104]: % d0 z3 G# i8 Q% X1 @2 k1 M  |
    0    ****a
    7 n3 z# o; K$ |! q7 N7 ]1    ****b' Y! X* a- W; B4 ~! n
    2    ****c! k% c! R5 ^8 u
    dtype: object; S9 Q; Y2 o- Q0 b3 e& b$ Y% j
    4 [! P: o; s! o
    s.str.pad(5,'right','*')
    7 I  w4 V. K- hOut[105]: / T' v9 j- G6 n. w
    0    a****
    # C5 J: e# L" S0 c1    b****7 A  k3 w! O7 p. _4 p) v+ `" V4 a
    2    c****- g- w0 t7 k" y; l: r) \6 _
    dtype: object
    - x! z1 f2 C: c! g7 a0 V/ a
    ; J! J! `: l8 @/ C+ ps.str.pad(5,'both','*')7 `: |3 R3 @( @' G
    Out[106]: 9 }2 y1 O/ d# i2 y/ I$ T
    0    **a**
    1 }* L  B# x/ g3 E" B. @5 |1    **b**
    4 ]8 d) E" {5 @% S2    **c**
    ' V; L6 B" u- L7 F6 W- ~! zdtype: object
    # w; F/ u. Q* `9 s4 r) L' b# c+ T  W8 E! p2 y1 b
    1
    # k, H* Q/ T& Y0 K- U! e- i* M+ r20 U- Y. ?; [0 M3 K+ Y
    3
    , k' k7 B$ p0 n% o4
    3 e3 m5 d. j- l" r5
    . N6 v( T6 o" V3 h; T68 r5 a- e' q( ]' U
    7$ G7 m; C3 T: b& |9 I7 Q
    88 ~: ]+ b" a4 c% l9 a5 f% a
    9
    . C. r& F1 C, |4 F% p6 r10
    9 x. ^) g9 p$ O" a11
    . ~  ~  `: B" V7 L0 n12$ K7 G, U# a  I! R
    132 x" h& y8 [! i4 c2 P9 S1 z4 W8 }
    14
    / }2 J  y* K6 W# z2 a5 r157 P/ l; ~( X3 B1 g1 R
    16
    3 R/ _( r+ z) o# ?2 G+ }) C171 w! ^& q( f% B2 ~" z5 Z* @* |
    18: |: Q$ ?6 a& s5 J+ w9 |7 I
    19" R  W" u1 O$ V- i
    20
    ; d! @% T' L' i& |) e2 d212 \- k& x4 k8 H/ p$ E' D% ^6 O- d" e
    22' m, F3 F8 }9 z# e+ ?* P
      上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:5 |0 @3 |# b# M+ P

    ' }+ G/ a' P& v: O. {9 s+ G$ S7 Xs.str.rjust(5, '*')" O3 f& _' R% l" Z% n2 w8 M0 _# T& h  U
    Out[107]:
    ( n2 r9 U- F: [" Z+ L0    ****a' `( D' m4 D) ^. k
    1    ****b6 C, F& D- Z' C8 R. J
    2    ****c" m( ?9 k8 N5 }% [$ W
    dtype: object3 ?( {$ [9 r" t8 l( i: d

    3 h, G9 N( L2 L- N9 o# ?% m3 ?s.str.ljust(5, '*')
    - {' q; V4 g& yOut[108]:   w& g: j; T3 e1 `4 z4 K
    0    a****
    ) H! S( n. H& M9 U1    b****
    0 p2 w) R/ }* e0 z" }) W4 \2    c****
    0 A* M7 b1 Q( adtype: object
    / [" U# q# ]; ]# Y' {/ S( {! {9 w$ ]4 o; f0 V. Q
    s.str.center(5, '*')
    " [' u' W+ N3 @* G& R5 j7 {Out[109]: 1 F, g* j# M2 c
    0    **a**
    : E! y! u: f4 A+ s4 _6 G1    **b**
    3 _* a2 m' C+ H. J5 S" q, P" U  O2    **c**) p( s* ?: e  J+ o4 P
    dtype: object& P, `5 d' K4 @9 W" |

    8 ]3 H4 u: V3 V9 o  s1
    8 v$ y# O- h* F) I. r2
    : k; y1 B7 P8 t: B* `; w; x' W3
    6 Y- A! M: j' z( G$ X  R8 n47 M, m+ h: @% Z$ B- X" w( B3 [+ D
    5  }8 |& e! {2 U" H
    6
    + C7 ]- i: O' X7
    ( ~# H5 W* v1 I% |6 P8
    ) a2 c! x  r9 {! @' ]9" `9 J3 B5 i7 ~  A9 j4 D
    10
    8 q9 H$ a8 U" O$ G% r9 A! t11
    6 J) L$ f- c: \$ K7 N7 e9 L! n128 U# C: |" w% I
    138 X: R; ]+ X" ?; j$ D) T6 d" n
    14, e8 X# c: u9 g& z9 W- m
    15
    4 @# r* i; H# [9 v0 m1 [- K* b16
    , v: c3 g/ K- ^  [" K# y* v& i: n4 U/ r17' w6 D9 K, @4 g1 v: B, o& ~
    18
      Z8 d: }/ |; r1 f, C  J19
    $ A$ f# w* i3 q4 k* Z0 ?& f$ v20
    5 h# ^( V0 _9 Z( F  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。' t$ r( i( B" |4 I  z
    / ]' E8 x8 @1 V! D# b) `, F& W  ?
    s = pd.Series([7, 155, 303000]).astype('string')
    ! c7 q+ I+ l, ~
    6 R2 \+ i( v+ X1 Ss.str.pad(6,'left','0')
    & b7 u) b( I5 ~: c, h8 T$ k/ zOut[111]:
    ! l. w+ [7 S3 L( E0    000007% Q! J/ D) a5 Q8 w+ X# f
    1    000155! O7 U7 x9 a0 L* C" q
    2    303000
    4 K' I: R! {1 A; ^( p0 wdtype: string- r. V1 |( _7 B- G# K8 d

    * z' t4 v4 r7 K2 a. Z( ys.str.rjust(6,'0')/ Z2 q5 @/ q9 \2 [+ f/ _- V8 g
    Out[112]:
    6 m1 w0 q5 y8 D6 J0    000007
    , R& k3 |2 G2 u1    000155
    ' v/ d! U+ V0 ?1 l1 j% \( W$ u2    3030000 a: T# e* G7 m' t/ u
    dtype: string
    ' g. Z. \, n6 ~* T
    - u( U" d0 V2 Y" n: Xs.str.zfill(6)7 q7 s/ L8 }1 U5 V; [& K
    Out[113]:
    * T' ?5 l. J. p0    000007
    + N1 N; i4 z8 Y. n& n2 @. c  [$ g" X1    000155
    5 d) z& @% B, p+ E- V2    303000
    3 P2 B6 x+ l& v& Y; Y( m$ Wdtype: string
    ( z" S" \0 K2 V: R8 s# t: o! t, M' \" A& Q0 p9 E9 }
    1
    6 A# O, ~* m! G7 q2' G% U! @- L! n  K6 ?5 b% ^
    31 {1 \6 H( H! g; Y' _
    42 Q- Y+ I9 w" J
    5
    4 r' g9 W# W1 X6
    . d% i/ t. x, y9 f! _- b0 j! R7
    . @# \" C: A) O. `: s( Q85 L/ x. [& s6 F6 H+ {( N: x4 {) x
    9. B& n4 I" v5 ~* P1 h7 ]) i5 ^( {; O
    10
    % j: ?$ W, J* ^11
    ( V3 ~. Q1 N7 M* ^& o12
    & y' Q) c1 ~0 y5 ?- O& W13
    & a' S# Q8 c. K6 N& B, i141 u1 p! ~6 u. J7 _1 K
    15% q3 G7 O# I9 z/ W" K, E, ?
    16
    5 p5 |+ M: ]7 h8 m5 c17. A. h& S1 w: \9 g8 ^4 q- m
    183 X, u2 w( `. V! G6 W* E. r0 l
    19: r* a2 p3 w' P  q: `
    205 [& b4 y1 |( }3 ]# l
    21
    . L8 e3 w3 B1 r: [) v229 j4 g$ s6 v" w) H, W# ]$ L
    8.5 练习) C; Y$ X6 J! K) b& B
    Ex1:房屋信息数据集
    & R1 }3 M2 d- p" V现有一份房屋信息数据集如下:) u, k3 ^4 ~' n0 ]
    " o, y. ?- {7 c9 R- x
    df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])6 \' y* S) G; l4 R( u
    df.head(3)( r2 Y, \: Z* P: Y! P
    Out[115]: 7 p) X3 m; Q  E% ]' ?0 ?. q
          floor    year    area price
    $ j# T! t. Y$ [3 F) R0   高层(共6层)  1986年建  58.23㎡  155万
    0 L) [( l( L0 }5 C# _4 h1  中层(共20层)  2020年建     88㎡  155万
    ) ]' }6 N" G: k& W: v/ ?% y& R7 T2  低层(共28层)  2010年建  89.33㎡  365万
    5 ?) {: d, f: z6 T# d3 `, O7 W1
    ) _/ Q0 j- f* z% D2
    . j: T% }- m$ I) X# ]8 _3 y4 m3
    # N+ N$ k. J, W6 J# I: C4
    & T1 |' N  E. p3 q( B0 i0 n5
    - E' k8 h/ h. @# ~6% P& Y8 H% S" }" V- }% w* C6 p
    7
    - f$ G' t- Y  D% i- g将year列改为整数年份存储。
    " _0 q0 B, u( o2 p6 B1 j将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。  r: S& |7 r: {& ^2 `
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数6 v  R9 T1 z$ _3 g# }' N; d: ?
    将year列改为整数年份存储。
    ' S* Q/ v) \' d! O4 ?5 t8 d"""5 P9 Y0 t- Y& K7 o( L
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。! P! A! T# d4 }) y  V' z
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,- d8 ]) P: I7 p, N, S6 }% B
    转成int后,序列还有缺失值所以,还是变成了object。8 W7 V" z" N" I0 R# K) G
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    " L+ x  m1 |5 L& R% @6 |$ b0 F- X. G+ f7 j"""* e, N0 o1 _- ~; N+ q9 i! c1 P  T
    df = df.convert_dtypes()
    7 B: Y, U: X# B) E7 Ddf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')3 H1 x; M7 T3 q& p5 x( Q' p
    df.loc[df.year.notna()]['year'].head()
    9 B& W6 l* B. d
    2 |9 k% y5 W# A/ \- ^0        1986
    + A) n0 f2 D& t2 C# U* [7 s) g" X1        2020$ E6 N3 M7 ?% b" E) B" G
    2        2010* f* T# D8 ?( G" B0 q
    3        2014
    3 Y/ B5 J% W2 w, `! o4        20156 M' `+ W  D6 \
    Name: year, Length: 12850, dtype: Int64
    0 P$ h! d3 F4 X" f5 ]  a* y
    5 ^! w0 p. u# j1
    ; U4 ^. y% R+ j6 E3 ~22 N! N* X$ {4 P/ L/ w6 L' v  ~; Q
    3( ^- L3 ]3 d8 l; Q3 r
    45 A. v3 o6 h, j" ^
    5
    0 B7 J6 L$ j7 O8 n) `6& @9 T1 ~$ ^6 k. q* o, @
    7+ u, s) W) {/ Y, d3 |
    8! D3 n9 p) l; K, N& L( u9 Z- ^) g
    9
    / n8 }0 I5 A/ @107 R/ l: Q2 @6 X& {
    11
    3 c$ m) Z& S4 H12
    5 ?* J4 s/ Z  _5 ^13! N$ G3 ]( N; f& H- ]
    14
    ( k" T" O. R/ f: Y. M0 `15
    9 K" X! P$ s% C: ~$ R$ M$ e16
    5 U7 b7 o, Q6 X+ Q参考答案:
    7 M* A$ D; E% _. |. u/ O% t5 W
    : |# X8 a& u3 ]不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么8 ]4 B, i# Z3 z' N

    5 |6 e* w/ i2 ~0 ~df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')   d# l3 x2 X; N9 E3 t
    df.loc[df.year.notna()]['year']( d6 s# E6 q  v- E
    1! T1 a& B! L5 a2 u  h$ w! C: U
    2( N0 s7 v) }3 _" r' P
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。/ n8 A1 d  W3 A1 r- h
    pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    ! T5 L# j. E& p4 Y% mdf2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次: i2 x7 i; T; f, R! C' @2 x
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    $ a, n) e: l5 T' |# n: D' fdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    $ z+ |3 Z2 t$ U9 Y  u& e: Ddf=df[['Level','Highest','year','area','price']]# K8 M; e) r0 |1 p- F
    df.head(); l+ F3 `! i& r! l7 R5 L: L
    0 c" J& q# }  V! k! r, e# X1 x: T
       Level  Highest        year        area        price
    8 k2 r( h1 ]2 f; ^0        高层                6                1986        58.23㎡        155万
    5 i% }: t! Z- J+ v: Q5 G1        中层                20                2020        88㎡        155万
    ! Q8 E& @. G4 j2        低层                28                2010        89.33㎡        365万) x  y+ [' Z& G
    3        低层                20                2014        82㎡        308万- Y) R( B9 C1 d- j( B7 R( Z
    4        高层                1                2015        98㎡        117万
    % M# D3 P9 Z, r$ `6 d$ J! t3 B1
    - K1 \4 v2 V7 s7 M& q$ T# e8 s. z2
    7 w6 e2 @7 I9 Z8 p* a4 J: k3: N$ s- a3 d2 q/ L, J; M
    4. h  |& H( E/ w% O" K9 ~  a6 O2 Z
    5
    9 @. {6 X& ^4 Z, X  @; S6
    " P* j! P; `; E) {& ]5 U6 b7
    6 t6 H- X6 G( A. h, W+ i9 c89 q0 L' ]+ G5 V  ?2 b7 D: N
    96 d  ^1 P2 ]- X0 p* Y- q
    10
    ; L$ [5 W* H  _0 R5 b+ D3 z11! m0 K, x) E" K
    125 `' O0 m& F+ ]5 }- c" ^
    13
    . G. x- r4 }$ i0 M* x: X# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了0 ]% e: f2 Z  @. s7 D
    pat = '(\w层)(共(\d+)层)'
    1 U" o! I, i& {2 \1 Unew_cols = df.floor.str.extract(pat).rename(0 f3 @* l0 Q( ]9 _1 r( a" a2 q
                        columns={0:'Level', 1:'Highest'})
    ' a) [! D' f: u) s: B* J) ?+ V: k5 d5 V2 z2 c! H
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
    1 w* C/ Q) A% C- b) B9 }6 m( Wdf.head(3)
    0 ^, k2 ]2 R# t4 r  f' Q
    ; N$ ^) p, w3 ^6 u, C' m6 rOut[163]:
    8 s# d$ c8 Z6 s* f6 Y4 P   year    area price    Level Highest
    8 g( n% R2 _1 o# ]0  1986  58.23㎡  155万    高层       60 P  o) [4 W) h4 d
    1  2020     88㎡  155万    中层      20
    " }: F/ a: o8 D) u# H1 ]2  2010  89.33㎡  365万    低层      28
    ! A0 J' i; {/ M0 I9 Q15 Z% G0 I. M' s% N- u9 f, _* x
    2* Q0 `+ J, H* ^+ e2 Z/ r
    3% C  U- }) p/ }1 L  t0 i
    4
    % o0 Q$ G* f6 z6 c( x) y5
    2 W% t* E# l" X  W. N9 p& E3 Q6, @6 D" [* |% S2 z" N8 l$ F: B
    7
    1 _9 @# N) z$ Z2 r$ `. \1 e( t, ]89 K7 t' a3 ?- X: Z
    98 Q& n% W) E8 a4 U+ P" \+ I2 L2 n
    10
    & S/ M6 K# \* d( s11" j. y+ G! U. r) h
    12; o! U5 m3 X  E, t/ Y$ T3 q
    13
    7 U& R, _( ]  l% p$ p计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    1 c+ e" k) j9 x0 x, V" _"""# e5 y- A9 ^. N8 K
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式5 D' D! H( ^$ J% u3 _! d
    参考答案用pd.to_numeric(df.area.str[:-1])更简洁4 m) X3 }5 g5 l2 \) H, m
    由于area和price都没有缺失值,所以可以直接转类型
    / ~  Z( D( U" g7 Y+ Y  s"""
    % q/ s) s1 r# s% Rdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))8 z& y8 K+ ^" X0 J
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')  W8 q: g- _- v' z& ?& c( B9 ?& l4 q$ j
    df.eval('avg_price=10000*new_price/new_area',inplace=True)5 w# H5 _, ~9 R7 B9 I& ~% t
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    ( x1 Z- s- c" L: J+ ]  m% n# 最后数字+元/平米写法更简单
    : M/ r. w# q: ^! T( D* z+ Wdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'8 c7 p  c6 d* w% D
    del df['new_area'],df['new_price']! W4 t! l) N5 a) H+ o8 l# U
    df.head()  p/ ^5 _, X7 ^: x2 D: b6 T

    . S8 m) u# s' v2 O9 U3 v- |   Level        Highest        year        area        price        avg_price
    + I9 F, I; }5 z7 r; x0        高层                        6        1986        58.23㎡        155万        26618元/平米5 u- o9 n) {9 _0 x3 V) M/ V' \
    1        中层                        20        2020        88㎡        155万        17613元/平米; \: k3 c% o# a; J: D
    2        低层                        28        2010        89.33㎡        365万        40859元/平米% N$ K8 S$ S( e( M" x! t2 Y
    3        低层                        20        2014        82㎡        308万        37560元/平米6 c# o% ~  `% j* p: ~- g; j3 ^
    4        高层                        1        2015        98㎡        117万        11938元/平米8 T2 ^1 m$ b$ Y% b" C- b5 k) i
    ( A" R1 V0 ]7 _- y" O! h
    1
    + Z' I7 x! K' p; M2
    8 d4 {6 c2 d6 m% S* P3) q- }. T1 I9 T
    4
    ! F) p2 C5 g$ A! [/ R' X  `) o9 z/ \3 u57 R$ I- F2 c. S6 K" v
    6
    0 W- p# ^9 Z" J$ L+ e$ [5 b78 C1 L  _* N/ N  k+ L' Q
    8
    " v3 K+ y9 q1 k. T" |( V8 D9
    3 M5 J9 `! O& O: c# _! X1 R10) Z( x+ m& D- ?% d
    11- ]# G$ s6 p1 }, x) g7 d, |" x
    12
      A: Z3 }: m1 c. ~- K13, n. l: M% G3 K9 W
    14
      d- _0 I2 f- X0 j# B" q4 U3 z15' x+ @. j7 i/ Y" O
    16
    , V7 [- V& f' C+ ?2 r17
      X) o( Z! x& W/ C' [18
    . r& F0 y. `& A191 u) U; Q; x' I6 {
    20: l; S) p- D% {" y' \
    # 参考答案
    , u4 r! C2 ?( B  |5 T( B7 Ws_area = pd.to_numeric(df.area.str[:-1])
    2 E% _( L) z/ a7 w( ?# xs_price = pd.to_numeric(df.price.str[:-1])$ T3 b% T+ h8 U9 r0 X9 g0 v
    df['avg_price'] = ((s_price/s_area)*10000).astype(! r9 U: V0 I% u3 R6 m
                        'int').astype('string') + '元/平米'
    7 X1 W; D& i0 I9 p! C
    0 ^. d1 V9 ^: Q1 ?4 z* r; C" Tdf.head(3)
    + E( E/ t, G$ ^8 Z. qOut[167]: * l" x8 X) j3 T- h
       year    area   price   Level Highest  avg_price
    # V4 m" q- x# c1 O5 t4 G0  1986  58.23㎡  155万    高层     6          26618元/平米  C( I! I' t9 U1 P" [8 F0 r8 R
    1  2020     88㎡  155万    中层     20          17613元/平米' j$ k) J+ y: R, |9 B
    2  2010  89.33㎡  365万    低层     28          40859元/平米
    8 {, E: {  ]& s4 E! c( G1. c/ X5 p3 K% F$ j+ u4 p
    2
    $ D3 V$ [* f& L; i$ H( ~( T, s3
    * p5 s$ f/ m1 r9 S- p8 l8 C43 t  _; f+ d  B3 B, e
    5& I- L. I. w% J
    61 f" y  R% Z+ V+ j$ U+ i
    7
    $ }( l, k) X8 M2 ]8
    7 F5 x* N2 z7 h7 n/ }- W  j9
    3 b9 z2 [3 t- N% p9 I10
    % z9 W+ `/ |( M1 m; q114 Z! c* l( U: S* ]* }& p$ J) q9 N
    12
      `, Q) ?9 ^2 d2 R4 _5 sEx2:《权力的游戏》剧本数据集
    5 d, V! W4 }+ Z* C2 U现有一份权力的游戏剧本数据集如下:
    ( L2 ]3 b7 Q8 w% V1 M3 N9 ?! i: }# z
    df = pd.read_csv('../data/script.csv')
    ; I. o2 ~/ G; }( x8 g: }  S" mdf.head(3)  J) I* G7 N  }8 R
    + a  f. u, c, C6 V
    Out[115]:
    1 L0 R: [0 `7 _  E) L( s9 x3 lOut[117]: + A! O* L4 l: E+ z. @
      Release Date    Season   Episode      Episode Title          Name                                           Sentence" O1 H! z. U" n
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    . B: R. L0 S8 k5 P& V  o1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...
    ' Z7 S8 G3 w* b% l2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    & B; {) z# X, j2 W- X/ F1
    , s. K# A! u, Q8 e2
    7 N5 G" R6 C+ }/ s* F) W3
    ! a5 V5 U, O- a49 y+ J/ z+ O0 U8 d4 }* V; O4 y
    57 d$ e$ T! l+ o6 M2 n: V
    6( R& Q# [& S" s+ \3 ^
    7
    * _* V: }) f& g/ I7 {, X8
    2 |/ u( z, ^5 \. S5 T/ z* X  ~9% E/ y4 X" G6 U- F7 v
    计算每一个Episode的台词条数。
    $ _6 G5 S* `; ?- k6 B以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    7 G& c& G1 [# r/ f/ Y$ S1 E若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。. S4 K5 u% _4 ~# T% C' O( o/ a- R) {
    计算每一个Episode的台词条数。
    . E: ?: f, l( }& y% d9 ?df.columns =df.columns.str.strip() #  列名中有空格
    1 D* q  B3 a+ L" e/ sdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head(), W2 e) q+ l8 B3 \: ^+ T+ m

      P8 J. a! i! N# K# r0 zseason    Episode  * S6 h8 `0 I4 z7 g* v
    Season 7  Episode 5    505
    ! l. C. r# E8 B3 t4 k4 L/ S' ]' uSeason 3  Episode 2    4808 E! N% S% y( d( c% @
    Season 4  Episode 1    475. ?; N8 [+ V' {4 N& v% g
    Season 3  Episode 5    440* T" r: e. x( h" b5 d
    Season 2  Episode 2    432
    + r+ l9 m+ |5 a# S14 f& B1 y  J, w
    2+ o, U- _( T1 n& X
    3$ N. }- n9 _" g* c  G! |& q
    4" G* G0 K4 q) u% w" P( B* N6 g  t
    51 M& J4 t3 Q- r; {! Z7 e: l
    6
    3 J/ w% s- V2 s* X# c76 |6 h4 k/ v" ?! _( }( r" ~9 h
    8, w2 F7 ?6 s0 c. r: i0 D
    96 n2 j% N$ c' i# n! k/ _7 E
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。  Y8 |1 [& J( K% s- X& t
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数. [- e( B6 }+ `# N
    df['len_words']=df['Sentence'].str.count(r' ')+1+ H6 i2 G* j4 Y& z) T
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head(). L5 A' B+ A* Y! z! B

    4 K" ]+ a' {# iName- s9 B4 u3 F1 D( p7 d6 z
    male singer          109.0000007 X; b& w- X$ h
    slave owner           77.000000
    8 G; U, G' E( v$ dmanderly              62.000000
    $ s, u, F/ E: X1 ^( ]1 y# W: n7 `lollys stokeworth     62.000000
    ) n8 L/ ]5 o" idothraki matron       56.666667. V$ h; E& w) z3 K8 i1 F% ?. d
    Name: len_words, dtype: float64% W2 L" `) L3 H3 H# p8 J
    14 t; M8 J' s  e
    2: b6 l* u" U( T% F" o* N- @; `: k
    3
    $ H/ P3 X5 I" c) p9 J0 p- U0 O( k4
    6 ]2 a9 _. X: ^' }1 ^* x# u5
    9 n9 U, z9 g, v6 G4 M6
    + d. U  R' O: ]- n7- l1 \& k( a* c. T1 t) {4 ~% R. d
    8/ m7 [, x4 w: b7 _
    9& k, S5 I: ~. l9 k# P
    10
    & m5 ~0 l5 [" X; K  N11
    $ m; q3 L4 I5 S: ^! F若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    ! V7 B" P, w; I1 M: v- Sdf['Sentence'].str.count(r'\?') #  计算每人提问数' R7 [5 K: C$ N6 c
    ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0, b2 j5 o7 K5 Q/ c: V
    del ls[23911] # 末行删去
    # i% F0 |" {& [: f$ X; r8 q2 y7 }df['len_questions']=ls6 E! p, q, y3 W9 G
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
    : U* U9 l+ k4 I8 e0 F6 y3 M: U
    5 T% S. Z* r8 B1 Y0 R6 E6 WName
    ; @6 X2 u% i$ ~' Ltyrion lannister    527
    , _1 M4 E  U* r/ Yjon snow            374
    % F. _. ~9 v) B, p, h. a) Pjaime lannister     283
    - a0 n, ?9 J7 z/ S; |* `( Farya stark          265
    1 c  b* d' Q9 f( N! Qcersei lannister    246. w0 |+ S1 u6 R' y/ A7 Z7 R
    Name: len_questions, dtype: int64
    0 s8 n9 o% E* H
    ; I. |" g9 ?9 P# E- x$ m7 n# 参考答案+ F+ D% V  s1 {4 m8 s
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))/ _8 V1 }4 Y5 V; K0 A! G
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
    ) F6 q6 e$ w3 t- u  m4 a7 S# W5 y( C$ @( o; H" i! _( ~
    1! p& K4 m2 v! c5 C
    2) L0 P8 D& F* J/ g9 e" f
    3: h7 N7 d& x  A) V0 d
    4/ i8 v. Z2 p" B( J+ r1 r4 L; ?% f
    5
    / e# A2 v( I# Y: t  z6
    ; F4 o. y6 g0 H: c' D7
    - V/ \! _5 n2 g1 g8
      t9 |! x- A' h- w. J9
    5 L: X& ]/ R; J4 c4 |& w103 M% G) g% b, H* m! u' j  {) L
    11
    6 Z' `" ?- t3 g  K2 ^12
    8 S) T1 p6 ^/ q) Q13
    " l5 S! T' [( `14
    + G; D1 N( K; n0 T; U2 \15# D' @; H0 |" F' S+ C- I7 c
    16
    9 B0 Z) a) O0 V% m17& j! J; }# }2 ~4 p$ n( G
    第九章 分类数据7 X7 f7 M% w# A& n: P7 ], f
    import numpy as np
    6 k/ P; j4 g* s3 }4 p% _. g* W  limport pandas as pd
    * ?" D  [! |$ }+ }  [& s1
    & F' K) D$ B/ Q5 ~1 S2
    ; a; |/ I' a2 `4 d" ~) w7 `9.1 cat对象# o4 b! o2 K) |* c0 Q
    9.1.1 cat对象的属性, C! W& g3 T7 v9 n4 ?' t: a
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。3 Y8 b; u  L' i
    8 }8 Z1 }' |% @# f1 l8 b$ M
    df = pd.read_csv('data/learn_pandas.csv',3 y! k+ \! }( t0 x
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight']), U8 J% s! q3 D5 z& |- L$ f+ }$ Q
    s = df.Grade.astype('category')
    5 x1 d" P( u5 }* f9 q4 S# o5 p' s$ C( A1 q+ Y* `/ D$ U
    s.head()+ Q- a& w% h7 Y4 r  ~; |4 `1 v9 ?
    Out[5]:
    8 r- J/ w/ w% I; @# y- i7 b0     Freshman5 t2 g& y" P6 E' O: M+ q1 r( ^
    1     Freshman
    ! ~7 H1 r6 z- g' H  j1 b2       Senior0 [1 f, Z' S5 w) i; f, q% C
    3    Sophomore
    2 _* Q% k$ I+ V7 R% M+ |7 M4    Sophomore
    9 \. D8 |3 X% Y8 jName: Grade, dtype: category' `  H9 E: M2 U( j! O" U* T
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    & [* w! U- g1 m: h1 F2 i1 H1, `, Q! \( V( l5 I' S" }1 `
    2/ f' N1 x# u0 o7 z: `' o
    3: B8 v9 E! Z) r" r" j
    4
    1 b1 \2 V! v5 f" V' K  }" T0 L5
    7 m0 E. {7 c3 h1 H6' ^& {7 B) u! R+ @* Q6 p5 q
    7# Y9 Y" z3 Q9 S3 |
    8
    3 i  z& P4 J& k& X. [7 r97 O5 a  b5 {1 I/ L
    10
    % ?8 N3 |4 ?. K0 S11* `2 Q% }) d0 w, J$ \
    12: E( x! _1 l; I9 G" d7 v2 m
    13
    - C1 y" N1 v1 Q4 Q3 U2 ^. f' N- Q  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    7 U; c) _/ T7 g) r
    . Q6 X: m7 b5 x2 B6 [. Ns.cat2 L, u: e9 J5 n/ U: G* V& ]( w
    Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>5 U9 o, D! \' l6 A# r& L# q
    1
    3 ?; h5 C; Y% r4 s; M26 ?. c4 B' T' I) G' _# q5 \9 X3 J
    cat的属性:9 \! \9 _' j/ ^7 |" P* N) E: D/ L
    ) T0 ~! f1 d7 I' y2 `
    cat.categories:查看类别的本身,它以Index类型存储
    ! k" K* q, W6 D/ _! Zcat.ordered:类别是否有序
    ) C& W( z, B" K4 ~) g, `/ acat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
    $ X6 m) z9 l- C9 u+ Zs.cat.categories1 ]( U  g2 a( ^3 T9 G% p
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')& f& D; n  [( f' @) m8 A! \

    ) q5 Y6 c: V. O! Q  ps.cat.ordered# l. \' U- b1 a3 c4 l5 z- s& K
    Out[8]: False, x0 L' w# d; i+ a; r$ w4 L* ~
    . i' I' y" s; Y$ |7 ~  I
    s.cat.codes.head()
    4 Z! Z0 d3 F- l6 j- U6 POut[9]: $ {! s6 U" N) i6 b8 T6 a( S" L
    0    0
    * ~1 t$ R$ M% P, @1    0
    6 E/ H. q3 B3 k2    2  k3 [6 t- u/ Z. \( n8 b
    3    3
    5 [9 o5 G/ v) Y* Z! v  y4    30 Z' [$ n2 k& b5 }$ o0 Z
    dtype: int8
    # w2 `" u% {6 d7 o3 i17 J, m6 O* F9 L' N# {
    2; c8 J) Z; v9 q/ l5 v+ L, D% H5 @- |  Q
    3
    - N( c; p2 V( F' D$ ?1 n" [4
    ( g8 ]. @( r' ^7 s5
    ! X$ `8 F  I  S) M1 T6
    , v. J- Y. A4 A/ r. L( j" P7
      A) L+ f% Y# ]  H4 m, j85 W3 Y; N3 m/ V% [$ U
    94 L7 g7 U  z+ t6 B# [! G2 o
    100 K+ N7 y# q! O. z7 h
    11
    9 B: s0 m5 r( X5 G7 }9 M) C+ \12  `+ G" N" {  J: d4 R/ i  q7 J
    134 D0 t" n3 `* U3 B5 F! ?
    141 _- l' J" v2 ^- _! J: f
    9.1.2 类别的增加、删除和修改  Q! G& D% t& t8 i0 x
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
    / K6 x9 O, e& d2 P' N' C
    ( T& C# l) ^3 F' L( N, \7 W【NOTE】类别不得直接修改8 F1 N( R2 t8 `8 A  t7 u
    在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    + O. H$ ?& ~$ j- y2 Q0 d$ X& R, o* t$ O# @; a- @8 D2 c, l
    add_categories:增加类别
    4 p% K0 I$ {5 M+ k2 y+ @s = s.cat.add_categories('Graduate') # 增加一个毕业生类别, ?, J. b4 a' U" Y' D
    s.cat.categories9 A! g" s) v2 {0 s5 ^# r
    / K! l+ J0 C* _3 M  _& E
    Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')5 K' p# A" u( z) |" M4 h
    1
    ; x7 z) X: z; |! r! H/ ]$ R# [2
      |9 n3 {6 B$ m* G. M3
    4 i+ Q( X; L: F% x# _4# t* _8 w3 M) L' J) s2 V" I0 _
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。# e7 @; Y) m. y: x- }
    s = s.cat.remove_categories('Freshman')2 X/ Y' K; ?! F  t( W4 z
    3 I1 a9 ?- K1 B2 {
    s.cat.categories) t5 e; X/ z  o! k8 D8 V4 x
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')4 I" x. n% M' B3 [# r; Q- p. a

    1 Z$ K8 }8 ~. w5 {6 h! fs.head()& j9 V2 v3 L+ q' Q0 |
    Out[14]: % W- U5 S) N4 Q8 ]$ F# O
    0          NaN
    ; f# ?) Z" N+ G7 f1          NaN( s+ C9 g, S$ B# l& A
    2       Senior
    7 m5 G9 z+ g- H' i" K$ l3    Sophomore
    ; W" I2 g6 a( j, \. e3 L4    Sophomore
    , h1 {( c5 }2 D, s) \Name: Grade, dtype: category. U4 E$ G- [/ o
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    0 P: E2 v7 |" G% G. X/ u  e' y( }% a1
    ' G0 d9 F5 z3 }' O8 R2. S2 e0 t- V  g' P" C
    3% {2 J4 I. v# B! I% S$ F
    4
    3 j. e( O0 T' N1 \( P9 A/ ]' j5, ~! V1 L7 }% u7 E+ M: D8 W$ g3 g
    6
    . x1 I! \; i6 ^- b) G/ P# x7+ G: G$ @; V7 q% X/ g! l
    8$ [( }0 _1 Z) @- ~1 w1 k8 ~4 r
    9
    % `4 q% H5 h0 w1 p109 b( S5 }! W- u' P$ m. Z; V
    11
    7 j) i+ z  T7 d' |  Y7 F12$ f1 B& u1 j( y% H) N: \2 ^4 N
    137 X7 B, O4 V6 p. v7 w0 @; |" I
    14
    * Q3 Z- X" i) }. Y% oset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
    4 u; @0 m& T/ |) q2 \7 os = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士, B% t7 z. c6 u- Z: c- M) X
    s.cat.categories' K8 N0 [0 V1 ?5 F  r9 \
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')9 L; p" F" [! ?- |; J3 ^6 h" ]

    2 W7 v4 _. h1 S* ~& S+ ns.head()4 R! \  M  S1 ^) y
    Out[17]: $ F2 ]% t0 q7 F( O
    0          NaN
    6 Y0 f+ r2 ~, y  A0 ^1          NaN
    6 N0 c; \! F3 b+ B/ p- O, ~2          NaN& a" E2 W; X; `% }5 e- J
    3    Sophomore
      A/ w2 o4 ~8 X0 P( p- `, V9 a4    Sophomore
    2 s$ t# n; D7 rName: Grade, dtype: category
    ' E: G! w( r7 b& e$ u7 Y* \+ l+ v% PCategories (2, object): ['Sophomore', 'PhD']( z% [2 `* {5 ~5 o6 ]
    16 ]4 _+ y$ @& M# _: E
    2
    # v% P* N$ V5 w, H* p/ Z- p, E* g38 U6 h0 ~1 }  J% q; P7 A
    4
    ; b7 w0 _! G7 z8 {* g3 H5
    : W2 ~  F  K. }# X+ ^  v61 ~& ^& s$ l/ q7 F+ k, `  d
    7
    2 u9 P5 d, V5 D1 _* B! l4 t0 {. L8
    % x9 S1 i0 ]2 t! N8 n3 D9
    2 k! r' [- j! B! y9 l$ T* @" S10
    6 D" q+ o6 Q. y11" r7 W; `! @* h0 D! {
    125 G0 {$ l8 a9 Y3 I" m
    132 V9 @3 @! n6 R! [# d
    remove_unused_categories:删除未出现在序列中的类别. O2 i0 p5 N2 \) ]. g
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别# l- ~; o) J# M4 ?" R- H; D
    s.cat.categories
    6 X" r9 U: p, {% s% P" B# G1 ]" L( Y# [% s" @( a4 i2 s) ^# n9 R
    Index(['Sophomore'], dtype='object')
    7 s: P& R! K6 @+ v: p8 G  z1
    9 k6 T; J0 J* N& }+ m/ d& p) A2
    6 h8 }; s9 l; }  A3
    ( [( v5 g: U; M2 y7 s8 u4- X% o! \' j$ j$ z% K- |, e! G4 M
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    . M& I8 B# @. l- h- G- es = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    4 i! t2 E# h3 Cs.head(), _9 k, e: Y% _8 Y9 \

    + W6 d; ^) t. k7 _4 U3 Y0 Q0        NaN, M% S3 D7 g0 E( r+ z
    1        NaN
    1 d+ \- E9 G; u2        NaN
    ! {. E/ Q% w6 H' j( r8 ?3    本科二年级学生/ n* N6 j; i- ~0 j  p7 H" t
    4    本科二年级学生
    $ I- X! O/ d2 ]Name: Grade, dtype: category
    2 `% J4 ~- G- R; oCategories (1, object): ['本科二年级学生']
    , n: X8 H( U* b, B# M1
    2 f, I- T3 z) i, H2
    . S7 S0 ^0 w$ o* C& f3 X& B3
    1 x6 Z5 F! S" \; y8 t2 P4" T- E6 Q8 p+ X+ O
    5. _8 t/ ~* u; v8 Q
    6& Z& _. n; N0 i) l7 a
    7& Q$ a# K. O# O
    8% }; y# K* o' m1 `% h3 c) p: Y
    9; c% `" M6 m" Q6 w1 c
    10! |# a# H- m$ Y; |6 Y) ]4 P
    9.2 有序分类
    7 k9 m) b1 X3 e* g9.2.1 序的建立
    % U6 ~& Q- b/ L& [" q% B$ i  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
    9 U8 k! @1 I+ n' }; `+ Z7 z6 _. {- Q9 ~' ?- F2 R' I/ N4 |
    s = df.Grade.astype('category')
    0 n' Q. y" }8 h/ O4 c. w. e) p3 a7 Ss = s.cat.reorder_categories(['Freshman', 'Sophomore',
    , m8 ]# H) M, x) J                              'Junior', 'Senior'],ordered=True)3 `* W3 @( ~: z0 ~: a
    s.head()
    0 h) b" s! \$ S5 d- |* X+ E: f& N5 wOut[24]:
    0 b2 x- q, o. J: m3 U& ~/ Y0     Freshman
    0 K% l. t0 T, l% A1     Freshman- p  a- e* A  a4 b7 V& c0 A. L
    2       Senior. [- b) J, o( @* g2 m* F
    3    Sophomore
    # g  [. ~4 k3 F1 u/ }" w4    Sophomore
    " @* Q3 X5 G: d0 {$ t! T& e- t# AName: Grade, dtype: category
    4 f/ C9 t3 y4 K' H; @  \5 VCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    , M& ~" U+ W4 w
    / F9 A% w0 O' v8 R/ u: V! _s.cat.as_unordered().head()3 [, D- E2 N6 w  W9 P
    Out[25]:   N4 i0 q. R# U( p) L) P* O8 z
    0     Freshman, |1 z, Z. \; c1 H: j
    1     Freshman
    * h5 X6 F( i% w' Q" y2       Senior
    ; f3 T4 q# ?+ X% p, A( j  ^, O* S3    Sophomore3 v7 M/ O8 d2 q3 z( o) r* a* Y1 u; H
    4    Sophomore9 x0 y' I. a3 W6 U% c. c# _
    Name: Grade, dtype: category4 h5 ?* i5 j$ Q4 s# }! M2 G
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']2 l, h/ y9 H; a* A
    . d# X' m! p; t( x" i0 ]
    1
    ' F8 L& V1 z1 R* ^2$ v+ N# ^% J4 R, x! a+ J
    3, z# E2 e; y$ w! v/ b8 |* s, p
    4
    1 W% E  W$ ?1 P6 s5- r- D! ~5 }. u4 n
    6
    , z$ n9 O" R% [% T3 D7
    5 i" [7 j6 b# ~9 q# Q4 e' R$ T8
    7 f" @) J/ X3 \* A% X& d+ @9
    ( R, i$ i& C, a9 J7 U% t10# Z2 S0 \0 B4 ]* H1 k
    11
    : h7 d  G2 Z8 ?12
    ( g" v9 b$ w6 @: N8 |; c13
    " o  u: i# I# v4 i14
    7 q0 j  \. E3 o. W' i3 k159 |' i: W" j4 T) M, O
    16( v; A( a% A8 k. P
    17: n( r9 R* K; E
    185 G5 E' [4 h2 D9 Q$ Z* T3 p4 O
    19
    & U7 w- o# F* U+ b$ t7 B20
    7 i: \7 }, a8 h4 S( A* Z21
    ) A! |" t: l" f8 F. V22
    8 p0 w/ D5 Q; E- l! @  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    % u5 I0 X5 `. |  Y7 t$ y* c. b! i" g- V8 L1 {, k8 T
    9.2.2 排序和比较
    ; O* C% L/ ?0 h8 \# ^* ]在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    , T) ]6 V; t. l. M: k, \
    9 t! l; ^8 s7 Y2 O: `9 o9 X  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:% o( f: S2 Z* X. }9 u& ]

    : ~$ A1 c( W# Y; Odf.Grade = df.Grade.astype('category')
    + _. P3 e" P6 X% |; g* Pdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    " a6 u* f, O0 H9 h" S& P) ydf.sort_values('Grade').head() # 值排序
    . T% }+ E0 Y" c2 dOut[28]: 6 _' F0 D  m: ?$ p+ J% P
            Grade           Name  Gender  Height  Weight5 ?7 L+ y' s" O# P! R" K' Q
    0    Freshman   Gaopeng Yang  Female   158.9    46.0. t7 I6 y0 W" M* Z; Y9 {
    105  Freshman      Qiang Shi  Female   164.5    52.0
    0 ~3 B# J* p$ B' \7 v) u7 o96   Freshman  Changmei Feng  Female   163.8    56.0
    , G2 {8 P2 d3 H# v88   Freshman   Xiaopeng Han  Female   164.1    53.0; m  G8 y1 c9 s4 |  F/ D1 p( j
    81   Freshman    Yanli Zhang  Female   165.1    52.00 d, Q+ ]* e2 M6 U6 p& A! v6 l. [

    # @$ }6 S! j. t& K, p1 t* R, Wdf.set_index('Grade').sort_index().head() # 索引排序
    ) i+ h5 |) j* j7 F5 v! G7 xOut[29]: 5 h. r  ~8 O& G, X8 J# u
                       Name  Gender  Height  Weight9 ~6 P+ A) F7 x: W
    Grade                                          
    ; U: S. }& w) T# P$ I6 V- }! eFreshman   Gaopeng Yang  Female   158.9    46.0
    . m* I; m$ f* `$ U  {Freshman      Qiang Shi  Female   164.5    52.0* O. \  ]3 N! h  R) p
    Freshman  Changmei Feng  Female   163.8    56.0) y* i8 |& {/ X  j, R% e) Z
    Freshman   Xiaopeng Han  Female   164.1    53.0
    # r$ {6 z: n3 i& R) g5 VFreshman    Yanli Zhang  Female   165.1    52.0, ?: w" T6 h/ x

    # A# x- R5 y) s  o1 r1
    % C8 h6 f) E$ i* f, ~; _3 S26 ?8 e+ D* T4 F, m
    3
    % @$ i; M3 b# y3 f2 H4 j49 E$ H* e# }/ _! c/ X/ b* m: G4 j
    5
      ~; {: x4 W4 G5 e- K/ |6
    : P4 O) t7 l/ r7 v7) c6 c# l/ p* L( u  H$ V  o
    85 F8 g6 f. E' m2 j+ ?2 t8 F
    9# h4 o2 U' k& e" p% i# V$ r$ N8 g
    10- q; _' V2 X- J7 z5 l$ N5 c- u
    11
    & t8 `7 I1 ^# Y* d3 v12
    : R0 M5 ]+ `! p13
    6 `" K; @1 Q1 ^" R; V; r14
    $ C9 _. O" X5 t/ B. |! e3 ^. b15
    $ E# Q  t. z' @8 G9 o. ^16
    5 U7 U/ E3 _9 |& L! {17
    4 u- b+ \; m' D$ G' n. L18
    ( U4 G1 e1 G5 g/ A+ R/ z! e7 f  D. T19
    2 Q7 p7 W9 I6 g% i20
      h' ^& B: z$ m8 D  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    ( A' i* Q, f) l1 `  a# e3 Z2 J" b  K7 f  c, ]
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    - x4 k" S, Y- R2 O3 V>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    + v6 P3 r# I/ N9 `- Pres1 = df.Grade == 'Sophomore'8 A2 ^, R) b) x
    " l* I% z2 }2 ^- \  b9 u
    res1.head()
    ! R7 y! l$ C( f0 COut[31]:
    3 f% o, M" W. ~7 a0    False+ |& U5 Y+ {( S9 b  h1 D% Y2 C
    1    False
    6 t. U! ~! D: j. [6 B2    False% x# N- P4 L4 [' d4 G0 e: G1 D
    3     True3 k- m, e7 [, f: F* y
    4     True8 V  k! A* S  X+ Q, B7 L, P6 ]
    Name: Grade, dtype: bool
    ( A5 M7 E7 P) m3 w! ]$ J' t3 q" P# x, z- p/ M& }
    res2 = df.Grade == ['PhD']*df.shape[0]
    : n8 T* L4 U$ h- W6 y% M3 t6 O  _7 @+ c: _2 u6 J8 b, K
    res2.head()" V( I7 w' C4 g3 f; {
    Out[33]: ; l2 |8 ?$ v8 W' y1 ~: x
    0    False
    ; E$ }' r1 M. Q" |5 w* J! B% x1    False0 u6 r2 t. X. V7 y
    2    False
    2 Z) f% p; X7 u% m, P3    False/ r# [6 W, a& ^9 v0 ]! A! m8 {7 N
    4    False% R7 H) j/ B; z
    Name: Grade, dtype: bool
    1 D% R/ e% N( N  v( O) U; h! {
    8 d3 u* m' c8 [res3 = df.Grade <= 'Sophomore'
    " C3 M% Z+ n9 M
    6 u& v) w" l4 z8 M$ Cres3.head()
    % \" b2 |/ @. Q$ dOut[35]: ' T- I2 x0 Q! t) `! e* f6 c
    0     True
    % M3 T) n# o* y* m" |1 b, _1     True& f9 L0 j8 A6 i3 ^) |' G
    2    False
    ) I2 ]9 q& o, ]9 P# I: P3     True
    # j# A) F* E$ u% q4     True
    , q6 ]- i# F8 j! N: F! yName: Grade, dtype: bool
    ( t. Z8 Q6 Z$ i8 @$ a- P! o7 s1 A
    ! w* C. H7 n) M% W# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。* v0 c8 Q. r) b! R4 ^& p# N2 J# e
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    7 j" Q6 |9 h0 y0 E2 S; N3 k6 i: o! `9 ]4 X3 B! t* @
    res4.head()- e8 l/ L6 d# `9 M0 K
    Out[37]:
    2 n2 l5 P. e4 z/ G5 Y: ?! w0     True9 [- o8 V+ h4 t5 W/ U5 O" C1 V2 j
    1     True7 H; m9 L3 v* e6 I
    2    False$ z5 u2 K* N- c* V& E( V7 D
    3     True& H8 t; `! C4 s, \
    4     True
    8 ~  R1 ~) W! l" [- sName: Grade, dtype: bool) p6 v* }$ v, N! b

    . h8 V4 p+ L: v$ G6 A) t' p4 \3 e1
    ( K' V$ a. M) F$ ?" F6 x2$ F* q' L6 l7 T, K- U, {! b
    3; _3 [% E- x, E7 q( q" j
    4' [  C3 U5 R& ?6 |+ ~' X
    5
    : f& i$ k9 H! X9 ~( a9 z6
    6 d5 K2 z3 E& t1 E: n5 A7
    9 [- A7 H) M" W( K7 X! D8
    + u7 }- J& u. N  d1 x9
    : N! U* `+ O9 B: g6 G1 {10- `% v  x" ^# b3 D6 Z) [
    11% D+ r3 @, T" _7 |
    121 Y+ o/ j6 v* @% Z4 T  G7 ^/ Y$ x3 u9 j
    13
    . k  z' N* Q4 ]# E; |  _6 w14
    6 R! O- o6 `, _) s1 X# h$ o# U15
    # T( ?2 M& L* Z% G. l16
    . b; v& j" W' v2 j+ o& T2 J$ ~" {176 F6 ~- l' c! d
    18. M2 U0 E* ^4 D& w( ]6 H
    19
    7 L" F% E. j( x) _9 e. j% @: J20; {9 {( k. R) `6 x
    21
    5 ]5 `& o" E! T22% N) B. O6 D0 p( p- {" _
    23
    6 P9 W2 Y/ F4 }9 w$ d7 D243 k' Q2 Y8 F+ r2 D+ j
    25
    $ o7 z5 k* V- \1 M' a  R+ ]! y26& O# p2 {0 I6 @' ?
    27
    : Z2 a2 j* h$ ~1 H6 i. h+ y8 O& ~28
    - y/ }) I# P( o29/ C: B6 ~9 `8 Y2 B
    30
    - x! q! \% W6 J: a& V319 ]7 a; n4 [6 z7 T/ K$ o6 P( S
    32" d# y4 v& _' s$ H" i$ {
    33
    ( H: ?- O+ z9 V) K$ Q8 R% R! b9 p342 ^* @% A8 W1 W& _$ h
    35  g( T8 E9 p) W  D/ X4 Q$ F: e
    36- ]) k  N  N1 I: k- Y6 J
    37
    8 t* R: g! [( a7 R% p- ?38
    8 b5 [5 @2 t% s8 }395 J; x: \" U* Y" m) N; Q$ s3 k
    40: ^7 [2 F/ E. H" Q6 v' F. ]
    414 p6 e& e2 M3 }3 f5 r! N' i& P
    42
    3 K; k6 J/ l1 x43
      k' ?% g: _9 f  C44/ W3 b; @4 y5 }
    9.3 区间类别
    ' U8 S; ?& U7 B7 c3 o9.3.1 利用cut和qcut进行区间构造4 q/ a2 L. u% n  |; r3 r) @
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    1 ^9 R2 i$ x' [- z( M4 p1 n7 A6 a7 ?0 r8 ~( t! N2 G, n0 V1 s
    cut函数常用参数有:: S6 E5 ?* U/ ?& d: s
    bins:最重要的参数。
    - q1 I0 N' U6 b9 Q( I& c. }- D' a( X如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    2 V5 l. D, `4 l: x  S" h也可以传入列表,表示按指定区间分割点分割。; S" c  w9 d2 I  c, I: D
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。3 ~. K; w, k3 Y1 J" O9 T5 ^8 o/ R
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。" C* j$ m' Z$ d  w; B. a
    8 t# R& S% F% A# ^9 M
    s = pd.Series([1,2])2 m4 I  f7 K& x+ I1 U0 X
    # bin传入整数
    ! n. o) K# e" T2 X& L; g, W
    * i7 e6 k! g3 Z3 B3 P! Q1 dpd.cut(s, bins=2)
    ! h0 p2 D* C: o& v/ B: nOut[39]: & S8 Q+ `4 z1 [. {. _
    0    (0.999, 1.5]) w8 W7 i: ~- p: o, q( W
    1      (1.5, 2.0]  x( G9 Q9 [- ]3 N7 R
    dtype: category5 t: k& l& K/ e. |7 h8 Z1 l6 e
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    - r% I7 U5 f' W; ~( e6 V3 I+ e. Q, |3 z) t8 V
    pd.cut(s, bins=2, right=False)/ Z1 T! e8 p( f% ~
    Out[40]:
    $ q3 i+ G) K) ^! L, X6 X0      [1.0, 1.5)( k) _- X( R- O
    1    [1.5, 2.001)  Q; l7 I5 B! w/ {8 i0 I* f
    dtype: category
      h9 b2 ~/ z8 q: ECategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    6 G- @- g. z: Q* K  O. N$ J3 L$ }- a1 d. j

    - {# S5 N) w8 j; T# bin传入分割点列表(使用`np.infty`可以表示无穷大):4 L- E4 K- F6 g
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
    7 U# O7 f. z8 V2 \: J0 g- s6 gOut[41]: ( H4 x5 ~- L- l  F
    0    (-inf, 1.2]
    / c) @% X  {+ O' C/ L: F: _2 l; p1     (1.8, 2.2], Z: ]2 |# E% Y& m# O
    dtype: category+ I! n3 P$ u8 y  D( l3 u. d
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]) i& X! R1 H( F8 c6 H# m4 K

    / `8 y6 C: g4 S- b. |1: P  P0 U" o( n6 u. Q  r
    2* E9 x1 {& T( i6 t
    36 u  B7 Q$ L8 ?6 k0 U' ?
    4" R% C1 W# a8 F) a; s
    5
    & p$ c" w2 u/ V5 H* C/ J65 k% H" q& x: |% U/ K
    70 y$ d% }, V& a8 j, X- t& p
    8- l# K% X- K% P9 v" U3 E+ K. D
    9
    $ ]- `' |% d/ g, O+ n9 y9 G10; m4 h# q) F8 M! n* x
    112 b6 e7 w7 F6 b5 p( U4 o
    12
    + m3 g- q( H+ {" ]0 p13; c* U7 |" p) a7 s8 O" N2 W
    14* `4 X5 {9 i1 V7 g- b
    15
    0 R# |0 f! n$ t+ `2 c' L16
    ! [. g% X# c# e6 Z17, p3 t6 ^" z0 t- s
    18
    ) z3 h/ j! @, w19
    ; I/ s7 `3 z& C; {6 M5 x5 c2 A20
    ' l5 L' a( S( P9 o# ]6 k0 v6 V$ l21
    $ w* ~5 `# i9 q22
    ; P% a" K! T1 w( N( E23
    + Y6 N# i! H+ t; f2 P% v3 O24
    # ^) l8 ^/ a4 {* w3 Y2 \$ ~. Q25
    . J4 l! w+ u4 X0 dlabels:区间的名字
    ! ~5 D8 I! B& r- Oretbins:是否返回分割点(默认不返回)2 z# ?' N1 L  X9 o
    默认retbins=Flase时,返回每个元素所属区间的列表
      M( y3 A% A. P- H7 I# Dretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值0 T$ _% O+ _2 j6 h6 O! \' G: S+ S9 z
    - P: f& `* b! I$ i
    s = df.Weight4 S3 v; V9 T& {
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
    . r0 ~% s" B5 i/ k/ t; Eres[0][:2]: i) a6 ]6 n. H0 f, t+ m$ q

    / ~8 n' W7 m' F  i0 lOut[44]:
    7 p: M( V; g9 J" g' ^6 w( R0    small
    8 J8 J7 L& Y& c1      big4 V3 i0 T$ @: o9 K4 o
    dtype: category+ y& q! U# j/ M  m* N. W% O
    Categories (2, object): ['small' < 'big']+ J+ v. o% Q" T; w8 y; O
    2 e+ c0 z) o0 m. P- N; ^
    res[1] # 该元素为返回的分割点9 m) u% p6 V4 `& P
    Out[45]: array([0.999, 1.5  , 2.   ]). S4 J3 x& m' F, n. \3 q( e
    1# z$ |8 D8 F! P3 s- e1 f8 _
    29 U: n+ K4 M0 _" j
    3
    . v+ I$ a" A8 S# `4
    , {9 p! E: R# b$ r9 z" g5 p! ^5. L9 N. P- C$ Y; m/ |! Y
    6! k5 K" F5 P- Z' x! s: w
    79 J5 H1 c' O& b* `% b
    8
    0 c3 B, ]) o# I/ q& @4 I8 {. W9! _9 g. K6 d) t0 |, E) \
    104 q/ a  V( S/ R& ~# v0 @1 M
    11
    1 z' ?. W& `$ @! Z+ N; h( c12
      {+ |& b4 z' H' J" {qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。& \' C" \+ r$ c; C4 {
    q为整数n时,指按照n等分位数把数据分箱; e( C/ ^- b* P$ z" A
    q为浮点列表时,表示相应的分位数分割点。
    # ~* ?6 W* r5 `s = df.Weight5 I- s* D' Y8 X+ J2 I
    / P# l$ T2 T" [" B- \
    pd.qcut(s, q=3).head()8 R) ?* z4 @( X1 l# N9 I8 J
    Out[47]:
    8 H9 m) i+ ?  a0 s6 f0    (33.999, 48.0]5 |- h; L8 A1 }4 |' O* A
    1      (55.0, 89.0]
    3 Q* p& m' j6 g: n: `0 d2      (55.0, 89.0]
    3 v' Z! A( t  q+ J' G" Z: e3    (33.999, 48.0]
      B( N" V+ F' d2 r4      (55.0, 89.0]2 z% D  D. x+ m( ?- `# I
    Name: Weight, dtype: category
    7 i/ ^! [* p: u  Z6 g* s3 R  JCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
      E7 Z. v; k) l9 F: ?7 H8 \" x, ?. O# T
    pd.qcut(s, q=[0,0.2,0.8,1]).head()
    , t* z" g: u, UOut[48]:
    , Y9 s1 ~# z5 z3 ]8 x0      (44.0, 69.4]* b5 f- q  B* B6 Y: B3 l4 P  c! J
    1      (69.4, 89.0]
    + M& A7 f: J9 L4 H9 w/ x. F9 z* |9 \5 K2      (69.4, 89.0]
    ; l  U0 \' s# x6 ]$ a3 W3    (33.999, 44.0]
    0 j1 e2 b5 G9 A: w$ D8 C4      (69.4, 89.0]
    5 V' b3 z* R$ d/ KName: Weight, dtype: category
    % G' Y6 V& G( |' _+ M8 OCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    & L6 \# `! a  i3 S
    9 U& {5 K) v4 ]$ i2 r) `1% X$ ?% y, y7 I: P" B( T
    23 [: x! e" A5 G9 l8 |2 t
    3
    0 j8 Y% T, ~, u4( l, R! t( r$ l2 y8 E
    5. u0 s1 e1 F* X* z1 T9 H
    6
    : B- w4 u2 @8 n# k, Z7
    5 m: |! s, ~! n  o8 \6 U$ j8. g( r& @, x% x: L
    9
    7 m6 n. O6 R& p( |10- `9 X2 M6 a& L
    113 F  M3 E3 `% M* @
    123 W  d7 ~/ P% }; X. u+ v& p: j# w0 G
    132 A# a+ {! a% \
    141 q/ k' ^& `* a8 {+ m% a
    15: O! P6 N0 Q" m
    16. @7 `# e1 e& J; r
    17# d: _, g, N9 c. w. Q/ r, P
    18
    0 E; L& k; {# u( k; X" A19( ~3 F3 x& M+ P. }  ]- y8 N
    20: U; S. ?+ i! r! {: s# c* E
    21
    # G$ w  A# t3 f5 O2 E* o6 F9.3.2 一般区间的构造
    1 {& L  E; A( H0 k) J4 i0 j  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。9 C- v6 T* D+ {4 \5 v4 y; j

    " O0 O$ X0 c! |1 \% Z8 }) s2 K  }! q开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    2 @) l5 d1 i9 e; o2 {& [my_interval = pd.Interval(0, 1, 'right')
    : D  L  B4 a# P; w" A: n/ e! y# S3 t  Z0 J
    my_interval
    9 b- t' o9 A* [: v+ K: nOut[50]: Interval(0, 1, closed='right')
    % H$ @" l0 v% U. Y4 i' G+ P1
    . [0 p, n* y4 j22 D' u5 c/ Q4 P+ x( S! l' B3 i7 k
    31 H) A9 S( W: W/ }
    4
    8 S1 ^: Q% p+ h( t7 W1 i  H区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。2 v# a. }5 |, q6 i6 K# ^- Z& f. K
    使用in可以判断元素是否属于区间5 k# ^1 d8 k+ {1 r. d
    用overlaps可以判断两个区间是否有交集:
    0 ?$ n/ N/ s& v1 P0.5 in my_interval
    : {+ o: S/ R' L8 \
    ! b3 {5 L) O" ?4 b: F( @True, c- A" ^6 U: h% [% Y
    13 B% n, U& }+ K, ~
    2
    2 z; \0 v; E9 u3
    $ e$ @! C* t; Imy_interval_2 = pd.Interval(0.5, 1.5, 'left')
    9 }$ P3 I5 L+ K, d7 a$ xmy_interval.overlaps(my_interval_2)
    2 D0 z6 s* G6 n# h: M8 H
    / H( `- [" w  b4 R. C  ZTrue
    & W* s) c1 F3 I# ]* J1
    8 \+ ]* T4 I2 d6 E1 `2
    3 P' `; }& w: l3 d3
    3 k9 W" d* `5 k0 ?& }1 G5 t5 y+ ^* b4 D4- R; K! D. A$ z+ D2 U$ a& N$ ^7 t3 J
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
    1 q8 R; |5 b# ]4 q
    : w3 |$ z! i4 }0 efrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    . C' ~7 Z! a- K$ k7 L7 X) Fpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    5 Y% f5 V, F3 t4 W
    $ y8 }; o9 w8 r+ ?IntervalIndex([[1, 3], [3, 6], [6, 10]],/ f2 y: r0 m& V
                   closed='both',2 h; W) n: ~6 ?2 e6 V
                   dtype='interval[int64]'), Q) ?( i/ ?9 {# `( X1 T
    16 }2 _8 |7 [3 Z3 |* e8 W' L7 v
    2% k. |. B1 i/ j; T
    35 C: X1 ?" w- X! Y2 U4 t6 h$ R
    4
    & o8 ^2 D. N3 n: H5 M, f! m5
    6 `. M  x, P4 {' Nfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:. @+ w) L' i& p/ l; L) F
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
      K9 }# G- P5 ~. x  k
    ; l( T1 `. \. c) d! A  _7 ?8 ]IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],9 Q- |+ n+ `$ Q& e' l
                      closed='neither',
    4 J  `' d+ g' q1 h4 h3 P. l2 ]                  dtype='interval[int64]')0 P' y3 n, C7 a& a1 J  a6 Y
    1
    6 x- b# G0 l) B- q2  L; X- ]9 @! D7 D  s/ _2 N( @3 p
    3
    ; b$ |! X% J6 ^# M# t4
    & I% f5 \9 A$ T5" c% _. M! E5 M3 H" u0 ^8 H7 {2 W
    from_tuples:传入起点和终点元组构成的列表:- f8 @! S1 D4 b" c9 M$ i
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    , ]" X" h$ O7 p8 z& {! g% x6 @+ X# q! e! k
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    , s+ d' R) U% |( C! ?* y9 _2 b# T              closed='neither',
    0 e8 c3 F, U" [3 Z$ L7 B# x  j* k! S5 q              dtype='interval[int64]')
    7 n+ q0 k4 \# D- w% G. {16 Z8 c: T) N& f! O" M6 N5 u* W
    21 d+ v% s- A6 W$ P8 ]7 R
    3
    0 i7 k  Z( Y3 u0 \: T4) x$ n7 B9 ^* i# x( V8 a; |
    5) G& y- F* m8 g4 D8 A3 K7 l7 X
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:% k# B/ f. G2 m/ a" [& P
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数7 l$ D6 b; W7 k* C6 V
    Out[57]:
    8 u( w; H1 e2 x  BIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    + m  [0 b* |" c! J: A              closed='right',
    4 l0 l; r: a1 F! @6 J, A/ f              dtype='interval[float64]')
    " v/ @" P3 b0 a7 P
    ) m2 g0 ~, b7 n7 `4 Kpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度$ v) a6 Q" k. w  G) H# w2 a5 |
    Out[58]: 6 n: M: `. E6 a) v2 S% q
    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]],
    % [' w  \+ d$ {, R3 w( v! P              closed='right',+ j1 J2 w* N# W- f
                  dtype='interval[float64]')
    3 N6 U% R) o" @3 b+ ]- I1* \  y/ l% o1 H% B( i8 S
    2! Y/ Z' y% ^$ s  y
    3
    + _% w+ o0 E) ~( s  ~47 O2 C( S9 c0 n
    5. i3 r+ T. C- o
    68 J7 Z- H3 g. z9 j' ?) _& F5 S# y2 c' V
    73 P; a, y' ^4 h% g  e& Q+ X2 o
    8
    - t/ ~) q6 e/ ?1 M9
    $ a; U1 F- Y# _/ G3 M5 u105 w) p$ J6 B* B6 }
    11; A" M6 e; k; |7 [$ L6 t# Y* Y) l
    【练一练】
    : U9 V# Q+ G6 _, ^# N, R- e/ ]- Y  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。+ z/ P' w* k$ j: \% [/ v7 t
    * I! J8 t7 Y1 f2 B
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。. t+ B  L/ i+ \9 r- S. o
    8 h+ q2 i9 E/ o) m
    my_interval3 g6 _: ]1 {$ n% @" w. I
    Out[59]: Interval(0, 1, closed='right')
    $ b% |1 r4 r/ [* I: z! ?. q- Z3 A8 r- I1 ^+ [8 A# d( A! I/ s9 Y  f
    my_interval_2
    : {$ S* e  Z! \& Z! `8 f# yOut[60]: Interval(0.5, 1.5, closed='left')1 b2 t& t+ G! m. x3 g
    0 {) \5 K! K3 u. d3 a  y3 P
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')
    5 [0 C5 w1 l$ ^% c8 t7 Z" `Out[61]:
    3 {+ Q1 C) E/ \* O% l' hIntervalIndex([[0.0, 1.0), [0.5, 1.5)],( @5 O: c6 Y/ |4 V; v
                  closed='left',
    $ n7 \0 m/ ~7 T, ^4 P# V              dtype='interval[float64]')% E7 @4 h# O3 X2 X: Y- F5 f$ G& J
    1, ?+ O: T; \# p9 a& [2 |2 Q: W
    2
    ! g. j3 J* i. r& b/ `32 R; _2 S2 T* F( w7 A
    4
      V; L: l! u/ _; l5
    . o7 S5 \8 x7 m4 t# j* x6
    1 [7 k0 w+ S! C) w2 ^9 S1 j7
    & J( q- u3 ?) D% O. @8
    & p/ V& M3 k: X; ^. k$ g9
    7 _9 D2 \5 O5 ?6 ^& E2 s4 V) e105 j- b& c, c- Y
    116 c' j0 z, _  }% V& k/ M0 E
    9.3.3 区间的属性与方法! B  w( o* Q$ q) s$ X$ D. C% b- |/ c7 x
      IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    ) s' n+ e( r) X+ ~$ a. C. ~6 R$ ?! a3 G; Z
    s=df.Weight; A  z4 J7 m: c! @1 i
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示) c! C4 U+ ^2 H( j- g  c* J
    id_interval[:3]4 M4 c1 w, R& b  d$ f
    6 ^/ w. M3 I- G$ P! P: x" j
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],) a+ P% x$ u- v5 U3 |! T
                     closed='right',
    5 E6 K2 i/ e- e3 b- O                 name='Weight',: m1 M# {, ]5 Y8 y$ F
                     dtype='interval[float64]')
    6 Q+ P8 p, ]& I! H6 p15 b8 W+ A2 A! {* H1 O
    2
      n$ r( ^) O8 L# @3
    2 y: q( s) u1 Z  s6 L$ a/ ?$ n4
    ; g+ R- W6 k: I3 U5
    8 j+ i% V! O0 e6/ p: |1 z$ W3 X; W! R
    7
    3 z( a. V- Z" O) {! {0 \! w81 B5 ^$ t* W4 l" i) k
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    ! M$ o# J6 i5 M* \  F: g/ Aid_demo = id_interval[:5] # 选出前5个展示9 n! f9 d/ h2 g' B% F* \

    $ ^$ _# |: m* lid_demo
    ; P, ^" y9 d2 aOut[64]:
    ( N4 X6 D- W4 h6 KIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    * ?8 L$ I3 |. Q              closed='right',
    : x+ K* g( c& W0 x) O              name='Weight',
    ; q  O2 u. q  U: k3 O              dtype='interval[float64]')+ Z5 k3 y- G6 c" f  H% K  ^8 Z8 h
    1 n0 x6 F7 N: W
    id_demo.left # 获取这五个区间的左端点7 P0 f$ k6 \# p! M( @" ]) G
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    ( g& V' g% \3 b# S$ y
    % P, @* F2 L: ]7 b5 }id_demo.right # 获取这五个区间的右端点
    + A6 @# D7 Y7 R1 iOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    ' l* {: t4 C4 p3 m
    % k. U6 Y  L; `1 b2 v0 c9 Bid_demo.mid) [; S" j+ E1 v/ r1 G# O4 S4 a# `
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    8 ~# Y0 l. D0 S
      P0 M2 r: v* p) C/ ^id_demo.length6 {8 i- `2 }; }7 d1 g
    Out[68]:
    8 a% L: I9 W$ Q3 y2 ~# x# h( e* B4 {Float64Index([18.387999999999998, 18.334000000000003, 18.333,
    ! t/ A# S: k' ^2 l: {4 t              18.387999999999998, 18.333],
    / q+ m8 S* s8 ]  d* E1 j# n             dtype='float64')! `$ S  [4 S) _5 \8 N
    % A# r2 M5 M/ Q2 d2 Y( G0 U
    1& S2 P' s8 ?) [' L/ J
    2
    6 z/ f- y" e% E" M, g3
    6 t- l5 M9 V$ B' U4
    * C7 j- m" ?$ f2 p7 x( |1 ^55 _7 d' L  z- W4 B% a
    6
    4 U# q6 s1 x6 o. ~& j7
    : C; h7 N4 V5 }7 u. Z" K: l. V; j8
    & }+ r/ I4 v( U9 V! X9
    & L. {! ~; V6 [6 a" q( ]: C/ G/ m104 \$ k" s% b9 A9 i
    11+ l8 h9 w! r( m% Y5 r
    126 k7 A) U0 E( N9 G8 n' P6 `
    13
    6 r3 a5 O& I' J3 J+ G% g14
    ; c8 G7 c& X( c9 J6 N( L15
      Q' {! ]( v% j16
    ' w' C( `0 X7 O0 D* l17
    2 @' J' v' m% S' j6 z( H! W! M18- T! E6 G, N( l' K  I" j+ R: A
    19% t# s' ~- Z; O$ \/ M# A4 O; V& }
    20
    4 j# d, ^* ^1 z213 R, |% S: h% T! s6 r
    22% r  ~; A# c" W% o$ N
    23
    # f( v  @- Y; ?# d2 q7 Q1 y# R( SIntervalIndex还有两个常用方法:
    8 B# R( D' P- v1 \' H! U. ccontains:逐个判断每个区间是否包含某元素
    8 j& \; H) S' B3 ~, S9 Z% u' r0 K( xoverlaps:是否和一个pd.Interval对象有交集。
    + e; d5 I5 ^, C6 K9 S* N( E. p2 Eid_demo.contains(50)
    3 F2 ]9 L1 J0 o0 c/ m. W4 yOut[69]: array([ True, False, False,  True, False])! F5 j% [1 K" Z7 Q1 W! f
    1 K+ t4 r8 d* H; T) P9 T
    id_demo.overlaps(pd.Interval(40,60))1 M2 Q0 j: p2 w$ x) N6 S
    Out[70]: array([ True,  True, False,  True, False])" f1 k' a, b/ ~  H$ `
    1
    6 U: J2 W* c# S$ L! t, S( E. Q+ {, M2
    " g; r) w& P+ {' @' [3, `0 v1 s4 A, Z: u$ {) |
    4
    8 f1 s* J6 M% E$ Y5
    6 s7 x+ W5 E' G$ o# v9 n: P9.4 练习* f: p$ }8 F- w( M  t
    Ex1: 统计未出现的类别
    9 p  ^3 }' k. ^, ^% {7 f* b: h  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:' ?4 |  [, c; z; z3 k9 {
    6 V- }3 x4 _! n/ P: V
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})) x7 s, P+ B5 w6 |
    pd.crosstab(df.A, df.B): b* x: u1 k7 G4 ?1 p: `' |% O
    0 l: R1 n0 W: x" l+ I2 _
    Out[72]:
    1 o- Y7 i0 Q; h5 [9 V+ RB  cat  dog+ N( x# l! S! S5 M" O* X1 ]
    A         
    $ ]7 r' h; L# X/ Y$ q/ Ya    2    0
    ) `  H8 z8 X$ q0 o' f; lb    1    0
    & O" |0 Y7 s2 ~* E$ {c    0    16 }6 U) N  O) G* h
    1& y& |- B  h; S7 \
    25 I& p% F, p0 K5 z5 S* f
    3
    # k, L8 S( m; B3 z( W& j* Y, ~4
    / ^( f4 z3 A! }9 W, p! t5
    9 M( Z) e& U+ _9 Z+ B) p) u4 D( o6
    " G/ J9 z8 r( b! Z& i* Q7
    + \/ ~* \" m5 a8
    : k; |" ^4 J5 _$ ?9' [4 D0 v: a3 G2 m' q) b
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:1 C  `; Z- I* v
    ! X- B# w4 v/ l8 F) f
    df.B = df.B.astype('category').cat.add_categories('sheep')9 m* o* T1 J' T3 G; I
    pd.crosstab(df.A, df.B, dropna=False)% y+ u% X4 h, s; Z

    1 m6 P4 x, R0 H' Z2 gOut[74]:
    " b5 ]5 {' S( E' R! X: J  j4 ?B  cat  dog  sheep
    , U, S" y4 Z6 i% M+ f9 u8 IA                 
    % i! u$ b+ K8 H2 U& J+ f1 ra    2    0      0, Z, g6 F$ R( E- @# Q* k3 k) L/ I! y
    b    1    0      0
    ( X6 E; }' Z4 _' xc    0    1      0: @$ `+ m( w9 r! ^* l
    1
    9 [6 L) S+ j0 N2
    7 }' i5 e2 b+ N. C( e7 F3  B, q- P! \: ^0 D
    4) [  q" F" e6 x( |
    54 e( a7 e3 E- @0 R
    6/ \& V5 i3 k- G/ G; O- a
    7: Y3 q" o3 q- z
    8* ]8 x) m1 D' Y$ h: o
    98 M2 ?( F  ^6 k: X, `2 k
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
    ! i4 c. h! a6 {8 B/ r4 U6 X
    ' m- m4 M. Q1 S5 H/ F* GEx2: 钻石数据集; U5 s1 ^% S# f
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:) M% h/ X0 O3 ~/ }4 z% b8 D

    2 H% j8 t0 R3 q' g* ?" xdf = pd.read_csv('../data/diamonds.csv')
    & }/ e) o8 Q; o' A) X5 Bdf.head(3); {$ K& g5 i, f3 H, h
    * K" Q! j7 B' w1 s& L: N
    Out[76]:
    6 F3 m- O8 y$ K, H   carat      cut    clarity  price
    5 M, M4 L0 J/ W5 ^6 R0   0.23     Ideal     SI2     326# g' e$ a' I/ _& r
    1   0.21    Premium    SI1     3268 _1 _' A# ?% U7 K
    2   0.23     Good      VS1     327
    ( r2 ~# v+ P" M. S1( f8 m0 t. ]. r) l9 c' O; }
    25 i. I, _* Z" X, L2 |
    3+ j$ ^3 @: D0 ~" G
    4# n# V7 o4 c3 [3 I# m& i$ \% N
    5
    ) y" {9 `9 x. U& V2 a6/ c2 U* {/ X, K0 D4 m) C- r
    74 x7 I  z3 G- N
    8" A) o7 r! `0 O5 @$ w
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。2 |1 R! c  A, \( h
    钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。; a* P( \% @! D" w
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    - y7 m6 b3 X/ O对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    : x/ }: u( o3 S9 n8 k第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。9 m) r0 H4 F3 k8 m% j/ c! U
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    4 L, v3 O1 y' i# Z先看看数据结构:
    + M7 U; b" \, M( Y2 c4 ]
    $ [) {" @( J, R; \$ ?df.info()
    1 F5 g" o8 P. Q" d# l% R  eData columns (total 4 columns):  }7 g# z+ `' Q6 H$ z' @
    #   Column   Non-Null Count  Dtype  . \+ B* {# L9 }- ]$ c8 p
    ---  ------   --------------  -----  
    ) k7 D8 h7 s4 K- D1 L0 N 0   carat    53940 non-null  float64& `; k' ?) ]- F
    1   cut      53940 non-null  object / G8 K6 g* ^& J$ A4 Z% z
    2   clarity  53940 non-null  object
      F" \; o1 X* s( o/ u4 K 3   price    53940 non-null  int64  , p7 c5 U. N# p' T: _1 {8 C, F1 K: B
    dtypes: float64(1), int64(1), object(2)
    . T2 ~6 {! ]( _" }: [& U1/ D1 C9 K$ w; B
    2' A# M/ W( R. Z0 n! E3 q/ ]
    31 q/ w: F. S2 `( @4 e0 Q8 Q% A
    4* v2 {/ K- H9 [( @
    5
    + j% G4 D* G% m- x# Y; F  q6) ?1 Z# {) t, F. v0 s
    7% w, ]5 G* v4 o; ]& }
    8
    # ~* X7 m$ M+ p9
    - y' E, b6 H; \* _, d4 Y比较两种操作的性能
    $ G& D4 i+ Q; u%time df.cut.unique()& G( S  D; A5 {
    4 S: t1 R- X# M, p$ ]9 O- [' u
    Wall time: 5.98 ms/ x  E! D4 O9 A
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)5 L+ Q) c8 a& X0 }3 r
    1
    ( C# z6 ?$ T5 H+ F6 B. R  j: n2
    6 b0 I, H* e8 q2 D. k% H1 \! y1 W4 v" X' F3
    . t* h3 c+ o, e/ r4
    " D- y( A: e; F) F3 t: J%time df.cut.astype('category').unique()0 U# d" w( `% j4 v& A, g

    3 `# \5 n$ e. u6 h4 B0 \Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms$ ~: A# y* x" _$ U( i' z
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& e! {1 p7 ?% w9 D( P/ ~1 A
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']6 w# M! I2 K/ C& V* y
    1
    6 F, \( N* J5 G2( T# m* \. H. H, B
    3. _* T$ S) Y$ U) l' ?/ F% L+ u0 j
    4
    # m" e$ c5 E% k% T7 B5
    + S% E9 M+ i8 C  d6 l( ldf.cut=df.cut.astype('category')
    ! ~& g% S( k  T6 |  }; a+ u%time df.cut.unique() # 类别属性统计,2ms
      N$ K- }4 ]) H! R7 C. _" L$ P9 X+ V; y3 H) ^* }
    Wall time: 2 ms
    9 E$ D+ z8 f$ h  r6 h; O['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']0 R' F: T# ^! X$ N% I  k, `
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    & q/ B5 I9 J. Z# b. w1
    ! X) K2 O! ^- Q$ y: H2% b: n) O4 E! @6 ]& E- \9 j, |# @
    3
    8 c$ @+ G+ J- u6 a; b( @# b4
    8 c: z- l5 a. N  |! U* W3 B5
    ) W. X7 G$ Z- W8 l9 ]( L# W" B6
    # N2 M5 G! t. ]. j3 a对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    1 D' C3 W9 Q3 F2 E9 u/ n+ zls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    ) \1 ^9 K! I3 q/ sls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    # R5 K" P& [5 C; V! ddf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    ' G  A/ l  d' \& a# V( |df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True); ?( s0 B( s! \7 u% w4 ~- w+ A
    1 Y5 F9 b. O- m, w0 X3 c7 i! [
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    : I! I8 `* o7 J7 w5 F7 f5 S
    ; T! {3 M$ o$ T, R7 d  Q  _# A3 X        carat         cut        clarity        price
    8 u3 s; E3 i. H9 ]8 v315        0.96        Ideal          I1        2801+ e) h" A* E' r6 k' v9 d
    535        0.96        Ideal          I1        2826& V% S6 X: L/ D8 g
    551        0.97        Ideal          I1        28300 `& _7 g/ M. z& l
    1
    % {/ B1 u7 a7 ~. ^2% l) l- r, m3 D6 |2 S; b1 N' Z
    3" q8 ?% f/ X; i) a# |
    4
    3 y; f+ M0 j1 n2 f; a1 O7 _5! ?& ?- C8 K7 ?2 Y9 U
    6. n. B2 J4 R4 W& `4 B9 m# M- v" T
    73 n( ], F& }4 C5 \
    8) [1 ~) P: T; F# F7 V) _
    9
    1 K) W# j$ w; M; f4 R10, T$ U; v# w6 i% Z& w
    116 w. Y( w# |4 E) D6 L3 H# N+ t% e
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    4 I! Z7 V* O5 V) U$ Y8 e. P# 第一种是将类别重命名为整数8 f0 d. H5 P5 K/ F- i$ c1 R- j
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))9 S- Z& f" w' |* D8 j
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    ! Q- v2 J1 p# b7 H9 U- v0 X& Y3 _' ~2 y: \& l/ W
    df.cut=df.cut.cat.rename_categories(dict1)4 y, L5 N% u' E- n( f
    df.clarity=df.clarity.cat.rename_categories(dict2)* B# ]. ^9 K. k% x0 j6 p7 u
    df.head(3)
    : f* ~! o& @# f+ l7 \8 x4 K, A" u8 f4 N( I4 d
            carat        cut        clarity        price1 A7 D5 q  b! _6 b
    0        0.23        0          6                326. U/ X- l5 O* H. m" R' @
    1        0.21        1          5                326
    ( w0 N; c* t# c6 K3 n8 A2        0.23        3          3                327
    7 K9 G) P# w. F4 l: q7 Y" I1
    % p9 \4 b$ Z# z! z7 M! ~3 O8 M2# ^7 T3 H4 W# m# X; m
    3
    ) B- U! m( i: ~3 t2 H( i; b4
    1 u( D  [; M; L- Z& q5
    / `0 {; }+ W) F6
    7 u, s$ z' w( d" C" \; E. c% v7
    ) y4 Q, \9 Z0 ?* i- Y! O; E8
    0 p2 q* `) U/ H* o: d! R/ j$ ~( K1 }9
    " P/ `/ g2 A7 G' P10
    % q. `" L' D7 @; b11) Y  ~0 B, }1 F" R& i* t2 s( ^
    12
    ; |, M4 v' ~7 R' C* t# 第二种应该是报错object属性,然后直接进行替换/ y9 q3 ]: o9 ?% ^( U" y
    df = pd.read_csv('data/diamonds.csv')5 Z( S' i7 h  Q4 A' R
    for i,j in enumerate(ls_cut[::-1]):% r! V8 y9 o7 y: A! K' k3 u
        df.loc[df.cut==j,'cut']=i " [6 g+ b5 c9 h0 F# _5 q2 Q4 E

    0 V, L+ Q; b, Q, K: q) Rfor k,l in enumerate(ls_clarity[::-1]):  h. s$ t/ C3 _8 K
        df.loc[df.clarity==l,'clarity']=k6 ^! ~. J& o( v7 m1 K2 n
    df.head(3)4 I! j+ b  Q" i1 T
    & Y* m3 l6 @1 F& p: R
            carat        cut        clarity        price
    ! z+ m+ O' Z8 T0 m+ D1 v2 G) Z0        0.23        0          6                326
    % @2 w. \* S: i3 _1        0.21        1          5                3268 E' k: p% F* ^6 F4 E9 @. d
    2        0.23        3          3                3279 ?& U4 |% g$ P% `
    1
    , z/ A) p) H4 ~& M+ k7 H# s2' m, g& l0 W6 \3 o8 f
    3
    3 u* l& N' @2 c- c+ M0 k% H5 m4
    9 y6 w+ Z$ M, ?: A' Z' d* p5$ D, R8 |$ }; F) i6 @
    6
    2 s* ~% L" I3 C/ d: ~& s7
    + {: v- r- t8 n" Z. P8/ ?5 f% s3 D) Q5 q3 m& p. `
    9, l+ U) l) q" q7 P6 x
    108 {1 T# I# [( r: @% T
    11# H! D/ ~% K" r  [  J
    12
    / p4 V2 l# A- z$ U, A- q. ^* |, ]13" r6 a* ?7 O, V: Z! U9 u" z9 u
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。3 k' q9 o& K$ v: {9 t8 j* ^
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    ' n- a0 ]3 X- `: Navg=df.price/df.carat* F1 _! O; ?' j8 m' q+ K
    2 E9 K) w3 w* u- m2 k. W
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    7 U! ^& J& F, U( X8 D) i; X                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ; W6 a/ G' }; k3 t# I. Y+ _! V7 j
    0 p, v3 \( p1 v* D- Ldf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
      ^+ C& Y7 t$ A) u                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]2 g) U; Y* J3 R6 k: I# y
    df.head()' s( {% z% T4 N, j6 i8 O3 ?9 d

    . t* h% T8 s! O6 G( B        carat        cut         clarity        price        price_quantile        price_list
    4 A$ D4 H6 x0 A3 \* j0        0.23        0                6                326                        Very Low                Low6 g9 i  {. f4 g5 J# z$ T" L
    1        0.21        1                5                326                        Very Low                Low
    3 e' [- m  j9 _/ f: u2        0.23        3                3                327                        Very Low                Low
    , K5 @) e) ^9 `' b' P3        0.29        1                4                334                        Very Low                Low8 N$ c8 s6 c6 s, [
    4        0.31        3                6                335                        Very Low                Low                                       
    ) k& o9 Q. h/ |% c: R9 p, X% q2 |' N* j
    14 R( v. y$ {. ]0 x3 q! A
    2) q8 o" Z  l& C7 w- [, ~
    3
    & G2 @& k2 Y' @0 Y3 D  j4
    8 `5 |. J2 j8 B( }( b57 z8 Q6 W3 J5 j9 _1 y* `' E
    66 y6 p( |( T  b* a. A3 h
    7% S( U( {, {0 K/ A8 c5 J
    8" P# _: T/ X1 b: u& s2 G
    9
    ) r! O8 `$ B+ M10; }* Y* d$ j" h/ C
    11
    ' b0 K3 C$ i0 F7 j123 C2 k: N8 y0 |, J
    13
    $ `5 }! N( O1 K9 [0 X14
    ; B. e$ F6 z8 k1 f15
    4 q" |" N+ b0 |" ^4 C165 z- n7 W5 m$ ?) L. [0 X6 @
    分割点分别是:1 `( S$ R/ |! g: W5 q3 j; s

    ) F% B# T- u4 F, y" V1 B8 Barray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84]): X9 Z( y; Y' a3 Q) l
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    4 {( K6 v& W8 V# ^4 K' R% b/ n1
    ; n+ C& Y1 G9 T4 ]" L2; D1 N" P+ F8 h8 n7 o; N/ H
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。; y' |# ~' U! ^) y& {/ n( Y
    df['price_list'].cat.categories # 原先设定的类别数' Y) N: ]% T  m  E6 B
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')( S& l0 Z# m5 z; l. U; b
    . Z8 ]1 @5 O* m  w
    df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别. R$ M) {5 D4 ]1 z3 m8 N" h
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    1 ^- f' f  P( J. H1
    ; K0 _) Y5 k2 l+ ], B7 T" Z& B. Z0 X2/ o. G% c. q$ t$ ^/ C
    34 q% i8 {" y, [  A& s7 K
    4
    5 r  S$ \# E2 j/ D5
    & L3 m7 B. y) f3 j& bavg.sort_values() # 可见首尾区间确实是没有的
    ' n$ L3 i; j. c+ N: X1 w31962     1051.162791
    % Z* q9 y% {5 ^; b7 U* V" k15        1078.125000. p1 Q* W9 I. h5 i/ [
    4         1080.645161
    * j% K8 ?/ m* h- q28285     1109.0909090 v# h. G& Z$ ?' ~) a# u( e* n) c6 J
    13        1109.677419
    - o" O5 U. E! v9 e5 P             ...     
    + {+ G1 U* r; I; p. P( {5 l+ r26998    16764.705882+ e$ G5 @# U6 E' u
    27457    16928.971963
    . i# [' ^7 E' J' _0 K7 U27226    17077.6699035 p+ c9 l. E# f! d1 d' R$ a- i4 b+ W
    27530    17083.177570; B/ e/ F' O( b/ o
    27635    17828.846154
    $ l  ?! N2 D& J" i) \6 W9 c. |1
    $ u. Y, ]$ M  c- H9 w1 t, d2
    ( _! ?$ Y4 B0 _/ y3
    * t9 I1 s$ U) W! y5 A4
    7 S. s" \. V: B- w$ }5  `4 [* D; `. V' k9 A- m) v' [
    6
    $ S4 G! u+ e2 s! Y6 q# k7+ l8 ?6 y/ U% f( k0 T( U6 D
    8
    9 C8 F) {( v+ B3 c$ e9: K8 M! G3 a, f/ u
    10( C( i/ O( F0 ?9 z1 {
    11. y2 q! U* E+ q1 D* ~
    12* g& L% t& R& B) A
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    1 N4 V4 @7 w3 ]& ^% b$ N6 I# 分割时区间不能有命名,否则字符串传入错误。
    2 e$ ]9 ^6 d1 |6 g! l  x0 Hid_interval=pd.IntervalIndex(6 \5 {0 w( N+ f
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]4 J$ S8 A/ U9 |1 ^2 y
                                )' Z/ Q3 n( @# A" G6 Y& F9 l7 A) h6 B
    id_interval.left
    0 W& u* [8 D: }5 ]6 U8 G* {$ z: Kid_interval.right
    * B0 K$ ~* {% Pid_interval.length                            - L) M9 A7 Z4 N* @  J+ y
    1
    - Q0 G8 e( t" k7 ^" G2" d3 q8 X3 n$ M% L1 y9 B
    3
    ; J: [! w- ?* {4: q) k) v( [2 r7 G' K! i
    52 f9 C$ ?. i( b% c0 y- e6 i
    6
    # e  e; Y& U7 C" \' H, ]: I0 s% t7/ `& ^- n4 }- N) [& g7 ?! a/ D
    第十章 时序数据
      }& y9 w* T* `' K$ Yimport numpy as np
    / Q: v/ e& c; B& Rimport pandas as pd$ D+ i) a9 C* C& {4 R' D
    1! U, u4 ~+ `- i% E3 D, z
    2, N2 j; K2 W4 [/ U; i1 J

    / O& j! H2 ~4 o1 r2 @& N" M# [# G! y1 k8 ^3 F# b
    10.1 时序中的基本对象2 s7 R9 {$ z1 I9 a
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    ( d  y- }5 P! F0 z% ^' D: X$ e! m% i' p, I0 |5 k3 G
    会出现时间戳(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的简写。
    $ T: m+ x* k! N" j- e( E) Z; e( `: r. x# X, n1 N( {
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    ) L4 P: |: D6 L( I, r: k3 ]+ B
    / F$ u. x( Z3 C. I4 A$ M& l$ y, w: i会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
    * e8 k7 O6 K8 Z- h2 P! r2 n0 D
      o2 k# P, k( l2 {/ L; f& V9 U' [会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。% a0 ]! x( L/ d' |" M" {

    - x5 u$ J5 M8 I5 o; c2 U5 {  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    ( S! m2 n5 @  ~0 f1 q9 g: v7 B0 j9 D2 ]
    概念        单元素类型        数组类型        pandas数据类型
    7 ?- Y( y3 ^* Z+ t# n4 ^+ W' N* sDate times        Timestamp        DatetimeIndex        datetime64[ns]
    * J: N( p: c" m, w/ o& yTime deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    2 `2 ]( e0 y9 C* A9 @9 q& K  K+ jTime spans        Period        PeriodIndex        period[freq]9 r, ^: {" P" ]- a  f0 |% o
    Date offsets        DateOffset        None        None- K% n0 V4 {3 C; A
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    0 y1 d. J; e8 S* }6 \4 U' Z$ Y6 M2 a4 e
    10.2 时间戳
    : I( u. s' R5 X6 k' [10.2.1 Timestamp的构造与属性' J2 d& c' J0 a9 ]! M+ B
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    ) A9 f# P# b3 l' u# X( n
    0 B! F/ O" k) {) R' |# uts = pd.Timestamp('2020/1/1')+ U2 E( T& X! u% z$ R
    ! M: }" W) h7 I3 x& {5 ?
    ts9 j3 W& [! _4 r, \
    Out[4]: Timestamp('2020-01-01 00:00:00')
    ! Z1 X- s& Q4 w4 G/ a9 R2 p2 D7 f) e+ G6 B5 |+ u- f8 c5 o
    ts = pd.Timestamp('2020-1-1 08:10:30')
    0 k) R; s3 P. O  ?) C9 A( Y+ B6 e' y
    ts8 {; M. L* T) @
    Out[6]: Timestamp('2020-01-01 08:10:30')
    5 E! r- S8 v3 B! u, b# y7 f18 _+ x8 I& p% x4 q% [4 a
    2
    5 v6 H! f/ {3 X9 M3
    6 P% x( }' k$ a; o46 H7 j7 D: V' ?( S( Q$ u
    5
    - z0 g1 J" F: i0 \& w6& z$ y7 u+ A( s$ d- T
    7
    ' b3 u8 U/ `" \+ A7 @  l# i  O8
    6 @$ L" b/ |+ L; d9 e4 ]9$ o% A' |& |( ?
    通过year, month, day, hour, min, second可以获取具体的数值:
    % Z2 Y+ d! k8 X, ?$ |
    ) Y: c2 N" y  Ets.year
    " M  e8 h9 H9 ?0 |8 B( AOut[7]: 20201 B1 S- U  U) m" F8 M6 H
    6 l1 f0 O2 j4 c+ m  X
    ts.month
    ' I% p/ {. O$ Q+ v/ ^" OOut[8]: 1
      S# W- l5 m; q9 }2 b, E# O4 [& o1 j, v% a9 ]# U
    ts.day
    : ^0 f% j  K" v# l3 Y0 c4 pOut[9]: 1
    : i+ C; n6 s5 B( y) r# R" i
    3 d+ `1 H+ S6 C" u; M. Uts.hour
    / t2 x& t8 _3 O# HOut[10]: 87 \  y! y9 W  |" {9 w5 T# o% M
    2 n5 j5 d8 G5 r5 D
    ts.minute! E2 D) u: P: @* y+ R1 E" o
    Out[11]: 10
    # t) ], m; k* r/ g0 R
    3 @% ?7 A8 Q' m. Q* a" w+ C* zts.second
    . |& x" s- Y5 d; ZOut[12]: 304 T" o6 o7 |. ?4 t3 P# @( T

    " L& O. ~5 `& V7 r8 I1
    6 O# t7 p; \2 O25 q; [9 l* c) Q! `! V
    3: X  X% Z! D% R) ^) G$ y
    47 ?) ]9 ]5 y( H4 b5 m
    5
    & `. W8 D; H0 Y$ M  }5 C6
    ' f# `5 [& D' G( A7, N0 b$ o! z* y
    8
    2 A2 |6 r% y- l: c9 J9+ J7 `7 J! _; Q! x& k8 P! r3 v
    10" e; ]& E% F0 ^( [% n1 W
    11
    : o1 @2 \$ D3 X6 g129 w$ s' p: B# v
    13
    : v4 \, C" t% j$ m) G" I, P14
    * t' a2 b/ `1 ]( Q15
    3 _) V' m7 f! D4 [2 a6 W" Z" ?160 S6 ^6 G' L0 P3 f
    17
    , Q( v$ o1 m4 {9 e0 y8 l3 O) g# 获取当前时间
      E1 r8 H/ u7 J" f5 nnow=pd.Timestamp.now()- O/ h* s/ l8 R- V. i. F
    1
    3 Z' H9 |, W$ F, }% @2
    % E, V+ X+ e" W8 S在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    ! ~% U1 p+ q4 UT 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)/ _) j5 x; p/ R! y' x  K
    TimeRange=
    2 e6 S9 `" x1 q; t$ J/ u$ h10
    ' s& s1 v0 n) N) O, f9
    : }0 i, d0 F1 e( }! G" m ×60×60×24×365
    8 A# J1 k: A' K8 o6 P& D: H6 [2
    1 b- G$ L& N7 O648 X* N* Z0 Z# Q( n3 ]

    ' }' W4 C. w1 C7 f9 N: u. M% \  w& B; ?3 e4 X- P+ U. e
    ≈585(Years): d9 e; Z( j6 n9 t

    0 z3 r4 p) ~# j) J$ Y. u/ n通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    / m2 a+ o# I- E  {/ O% F
    5 m# }  z2 ~8 k- E) Fpd.Timestamp.max
    . [- X/ B7 J8 f0 g  {9 {Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
    + D' f$ d) q( m0 d0 Q2 k; X
    5 G  @  O0 ^+ }/ H: X+ h) {pd.Timestamp.min5 U  `  x* u  l
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')
    ! f. A. P' e* [9 U& l% A# a+ f' Y$ K- w" n
    pd.Timestamp.max.year - pd.Timestamp.min.year8 a' y* Q% s2 b0 F4 W7 T% E0 I
    Out[15]: 585' L6 Z1 ^0 @  r# i
    1+ L; |5 Z# L0 u4 T5 ]
    2
    , e6 I# t! y7 d) ~: R! c3
    + i5 D( d& V$ ^9 G* [2 a4
    7 K+ Y) O* _4 B- t; T1 h5( i% a6 b" J/ L( Z
    6- Y' \* F0 Z5 Q3 v7 ~* k- t
    7
    3 o* o. S5 G) D, s- V) b. V$ m82 w7 s- g: ^0 m* m
    10.2.2 Datetime序列的生成! f3 l6 E9 k4 J0 ]: Y
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    & b1 z" l9 h& u                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)0 g( Y$ q9 o( B: f/ e# J5 |- W4 v# G
    1- K0 P' o1 c' |5 ?' j! z4 J
    2# r! C, S! k/ e  f3 h2 w+ J1 `- W
    pandas.to_datetime将arg转换为日期时间。
    1 h% ^4 M" H- {/ I9 k& ^  G: ^
    % a# j4 V4 A& L+ Rarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
    , y' g$ o2 D3 terrors:6 Y& S; x: {9 S
    - ‘raise’:默认值,无效解析将引发异常
    5 }! U& y! b* J6 d$ t3 y9 z9 h" T- ‘raise’:无效解析将返回输入
    % o" a. q; c+ P3 {- ‘coerce’:无效解析将被设置为NaT9 n% V3 N8 E$ x6 y+ N
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    % |# o8 ^+ \5 Z; Z8 ryearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)! ~! d7 z5 O2 u- H0 z: q( }
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
    3 f, L' d) N2 C% S* H" d7 P$ [* Eformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。; x$ r+ g; }9 X- M' q& O
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。) `) X. T; L: R( [8 \
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    2 ?6 j$ {: x4 v+ F- opd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    . ^. y& m) X5 |1 c/ f$ \9 }4 C- V7 Q! e
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)4 O' N! [$ q" o6 H1 s- \
    1
    0 x4 u9 n( Y& ?& a+ b! k3 L; t: Z2
      w# P1 c  L8 p* G8 }3+ O' {/ u6 N9 X- X) _& t
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    / U8 q, a2 J( g9 F% k# P0 |
    $ S0 w$ ~) I3 {& Z% Dtemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d'), \# k, A' f3 u6 z6 \7 h
    temp$ x+ Z% M6 F  `
    + i, m4 u/ ?& h8 M6 z( w" S& l
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)! }  b2 L% p: O& k8 W7 m4 \8 k
    1+ Y/ `8 u: [! T
    2
    # x/ q' f, n4 i2 z32 J5 `; A) Y! r3 n& N/ t5 \
    4& G3 N& w6 E( D; a' v6 d
      注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    2 h1 J  V6 e$ g- w1 w& j0 @! h+ a' J3 z, l3 V
    pd.Series(temp).head()* w' y) c" ]% n% H# E7 z* I" t7 P

    . N' N  Q$ l% V' \* J0   2020-01-01
    ; W  J  h& ~5 ?9 B6 c1   2020-01-03
    ! W3 m: |! r* N& v6 Ydtype: datetime64[ns]
    $ |6 S& }9 W6 [) e6 S' y) F1
    - D+ b+ c- b7 f2 o2) W3 W8 D$ h! x% X/ e3 T
    3
    ! ^0 g  F3 \# n9 w4
    $ Y: @' b: o' c5 o. r2 [- h5
    1 \* X  W( K5 G' O2 ]5 N/ e7 k' }下面的序列本身就是Series,所以不需要再转化。
    # J% e7 L# I, {( `8 p* [" h5 y4 r$ h' Q2 l* b
    df = pd.read_csv('../data/learn_pandas.csv')
    + F  O+ O( O$ Cs = pd.to_datetime(df.Test_Date)
    5 z+ T( s3 c5 R! hs.head()
    ; `  @7 ^( ~6 r& m: ~5 |5 l/ N& o* v7 y$ y; ?( W7 b
    0   2019-10-05
    ' C1 p+ z( z( x1   2019-09-04
    ; v' N2 a: h: C, e  ^2   2019-09-12
    ; D; b' G/ {( H3   2020-01-03
    4 P9 r- Z8 ~5 k6 d; s0 S) [4   2019-11-06, l9 Y" Z' Y  i( P& S' [) M
    Name: Test_Date, dtype: datetime64[ns]- D$ z0 o/ g. K8 C4 |+ N
    1
    0 U+ a6 Y6 k: Z2
    + H3 t9 a/ I% f  N; c3 @: }35 a0 ~1 v. y( o3 e# a2 |$ w
    43 q" [# r' x, G8 Y( _
    5
    " f1 x0 _7 c* o" B6 Q6: y! g9 C1 F1 E3 ]: _. w. W, _3 {
    7
    $ a$ M7 w6 K( T( v0 E8
    " E& i; o! X" n9
    $ n# J5 o  {" o2 j$ b/ j101 y! R& \: a+ F6 Q6 q
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
      P7 n) y; P' U7 ]/ ddf_date_cols = pd.DataFrame({'year': [2020, 2020],
    ) j. E1 a. }1 p1 W  T& C  r* Y                             'month': [1, 1],
    6 }% R% C) [9 V2 r                             'day': [1, 2],
    ' X) J+ x9 q( |* G8 h' V                             'hour': [10, 20],- u& t5 R8 O0 H4 N( y
                                 'minute': [30, 50],: A6 r# _* V0 u( E; }8 S4 T
                                 'second': [20, 40]}), S2 r! M( D; v& P$ Q% N
    pd.to_datetime(df_date_cols)
    ! F' P( U# a$ ?( u! I6 G2 _9 t0 v1 ^* C) w& P$ ~5 ~" ~
    0   2020-01-01 10:30:20; i( C$ R8 v* l1 u
    1   2020-01-02 20:50:40
    ' V( O/ W. o( R4 X0 }dtype: datetime64[ns]$ k( P" w/ i. }  d3 J  C
    18 |1 L2 F7 o1 H* M" J0 N
    2
    6 d/ L7 r: m9 k/ R+ N3& ~. ^0 n& A, h0 F3 V/ {+ i
    4
    . B. a& S, A" k; E4 D5
    " D, l0 E6 V3 c9 O& V) a& {* l6/ M. z; i1 t$ r! N! R) j# J4 [+ I- E; R
    7
    # l# g3 T: ~# T+ I+ T% ^8
    % }8 r0 `; c8 ~6 [9
    : e0 N9 V# s, ]' C5 @) o10
    # e& R6 J6 z) E0 v7 A2 B7 ?4 v11
      e' P/ s* p0 j/ Zdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:; n- u: b. h, g: I' I% ?: Z
    pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    . F; m; W9 @5 [Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')/ x! I6 o5 U/ S5 `( r+ ^

    3 _4 L# b+ B, t/ U9 o; U* Z5 }pd.date_range('2020-1-1','2020-2-28', freq='10D')
    ; Z4 R% P  Q- L) S8 i  F, e6 vOut[26]: # j& ^" u) f0 }* T5 @3 i3 g/ i
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',6 M! a7 y1 `: H4 ]9 K) J
                   '2020-02-10', '2020-02-20'],
    & @- d; M, I. E0 H( N2 h              dtype='datetime64[ns]', freq='10D')
    3 o% S9 P/ ~0 A! m6 S; s1 z4 M
    1 r1 e# E; Y+ q+ |pd.date_range('2020-1-1',' ?& D% U6 A3 E( r5 J: y8 @9 T
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
    4 n/ p3 s3 ]! h9 V) _8 ]& r8 J/ K+ @$ R. ~; b4 t3 ^. s- Q
    Out[27]:
    % k) S; s, c7 D. xDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',: {: e2 O, G; I* M9 K6 C( \' S
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',% e: H& ~$ P8 O1 P/ `
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    ) L' V: |& G/ D              dtype='datetime64[ns]', freq=None)- o" d. a' p/ I+ n  K

    4 v" z. q) |2 O, M" ~1
    ; d0 |6 N6 ?, z. {25 p# s' z1 [' t/ {0 \; [8 q
    3; X4 H7 w) {) U  [
    4
    3 }- y6 |. _1 y54 @8 @2 y) V/ G) V; u+ t
    6( w) Q: ~. [: B' h! |, q; z' h
    7
    + H; _/ k5 G9 H7 Y6 e2 x3 [" g8& x# c. d- Q- M& k+ J$ k
    9
    / ]1 A* E- @7 |5 B) t101 D3 X# ^0 X" L, e. o* U! _2 x/ L
    11
    1 ]9 K. x% h$ J% a12
    ; q: I" P8 I: _7 x% z" `13: H" C$ w# t% B* j  c
    14
    3 r9 ^2 p2 n& p. y" f9 }6 Q, n+ m15# T7 B) n' G* M# S
    16
    7 j0 a) l8 p. l" M! o171 G9 @  k3 P$ G8 Z" {
    这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。: w9 k. ~& U8 P
    $ Y9 X6 G( t$ v4 W: x
    【练一练】# x, m: F* r5 R8 [6 a# e( E* I( Z& s, p
    Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。4 s+ V, \5 j, t

    / N+ u( t7 w4 X4 `# m* \ls=['2020-01-01','2020-02-20']
    - a' X9 H' f) L! P, z) adef dates(ls,n):
    - p. u# ?' N# K6 Q! Z/ c    min=pd.Timestamp(ls[0]).value/10**9/ Z$ R( Y: R/ V% C
        max=pd.Timestamp(ls[1]).value/10**9
    8 B! Y) [) ~( t# C    times=np.random.randint(min,max+1,n)5 B6 h% `. |: r# i5 |* u9 M! i; n
        return  pd.to_datetime(times,unit='s')
      S) _+ b( C  B4 I) l( J9 cdates(ls,10) % z/ L: a  P8 }( z  E9 b
    ! y8 B+ ~1 Y' H: m9 T9 A; X
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',' F* z. A- q6 _% v$ ]" S8 Q% W6 P" ]
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    3 x7 \3 r5 V: p- S& `               '2020-02-15 00:18:33', '2020-02-11 02:18:07',* d6 L( J8 n4 X' U* U5 T
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',, l$ w1 Y" L' V
                   '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    ) e8 V0 z6 k  Q              dtype='datetime64[ns]', freq=None)
    ; Y' d# J6 E' U8 z% K! o2 ?1
    , j; m4 A/ k6 u# z( s( i6 u26 j8 x) S; Y0 f: @! l5 g
    3- M' M+ @$ D2 L% P: Z- t' V
    4
    " a2 R/ J* G. n. `5
    " @/ q) f, g  B: N, L5 N6
    # M8 K+ j* Q6 _/ O7( q0 E1 O4 u6 n0 u7 b
    8
    1 w+ @2 e0 \) r& c9
    . g/ n% e7 W1 d" h10! y+ u  x# O3 M# j4 ]
    11( F3 N$ h7 d4 _8 c8 w1 P0 q
    125 u1 R- \* ^7 ^5 e: Y5 s
    139 l: g4 D5 U" N  l  D
    147 |  x: v- w% z" K; D
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
    ; x/ N" c/ H3 W0 p+ u* ~, U: A# \s = pd.Series(np.random.rand(5),
    ' h# }1 @. j# B6 H2 L            index=pd.to_datetime([. Z6 {" u- x  ]  o, z
                    '2020-1-%d'%i for i in range(1,10,2)]))- F1 v1 J* o* D2 @8 {4 Y# s

    , }4 a4 Y. W! W: o3 b5 |. _% m+ S# Y3 j! X8 q% `+ v
    s.head()
    : l! U) Z0 n/ D& \$ ]% OOut[29]: - |# Q3 v5 G  v$ s, ^
    2020-01-01    0.8365787 D8 ~+ s  g1 p' A& R' ^, W
    2020-01-03    0.678419$ H& Z  G! o: ]; Y; {
    2020-01-05    0.711897; Y/ A5 }- }, A7 @. r
    2020-01-07    0.4874291 a- C8 P9 Y, n; V# z# k
    2020-01-09    0.604705
    . c! w1 s0 L, Xdtype: float64
    & J; {- ?# e+ ~, i7 P
    7 P! ^. `6 y$ ^* Bs.asfreq('D').head()
    ! g' d; v. ]7 w/ a- EOut[30]: 2 n, m3 X. z2 _% ]- Q' z2 B4 f6 O4 F9 ~
    2020-01-01    0.836578& @6 k6 y  |/ y9 m$ o
    2020-01-02         NaN
    & A7 p2 V; ]) f, r- ^2020-01-03    0.678419" u8 l' ?' d! V0 k$ D* b
    2020-01-04         NaN# r2 c- a  v! o* ~$ [6 ?. F/ D7 Y% l
    2020-01-05    0.7118977 a3 K$ Q. m+ ^9 a6 l# U2 r
    Freq: D, dtype: float64
    : i3 J- Y1 n( l
    . p) |0 ]' ^' ^s.asfreq('12H').head()+ Y- H1 Z( l, N
    Out[31]: 3 f9 b  a' `- T+ s' T  e/ O
    2020-01-01 00:00:00    0.836578
    ; G0 T; G  ]) H( O( c1 r2020-01-01 12:00:00         NaN
    7 k9 `0 W3 ~3 A9 e$ J; X9 [8 \2020-01-02 00:00:00         NaN9 i& M/ X+ ?0 m. P" v
    2020-01-02 12:00:00         NaN  D8 _9 q7 n, ~. I; J
    2020-01-03 00:00:00    0.678419
    : S+ N+ O- D! M2 X) n) OFreq: 12H, dtype: float64
    1 G% N1 }/ v/ F3 C, J, h/ k9 `
    # m5 _6 e3 n/ n( J2 t17 Q7 r- G, m# Q
    2
    . o3 d+ q; U+ q, ~& B! S8 s0 U35 u) R1 C6 K' Q" u6 S. J
    44 H# G0 ^. ~3 T. E5 l
    5
    ( m6 I( L" j0 _6
    7 Y/ l5 W# u; e1 r1 w7
    " c+ S6 N% V# D1 `6 p8
    ; K+ t1 m/ `2 d* U3 _9$ w2 V! \: w( V6 x
    10( G- E4 y% A. |" g; [& o
    116 p2 a2 D1 i2 T/ B
    12. O" V7 v9 u; W/ i: q6 U* |- F
    13
    # ]) X- l2 U7 e8 ]9 B14) _; h2 p6 ~6 c
    15! O* R( H& T* V0 a3 y
    16
    - o% R2 n6 G; u) {, \17
    % U! v( _3 P& V# ^3 u# P18
    ( Y( u  k, J' g9 K19
    % A2 \/ O0 p) p; c* V. m, M2 t20
    . u; K1 k/ b; Y6 V2 G8 q# F21
    % h0 P7 ]- D7 J  @; s2 Z: ?# b22
    9 W) M  ]6 ^# M( {. \; B5 }; k  @23) U5 F6 Z& D$ g1 y
    24/ T* W* d" V* v
    25
    $ P0 ^2 [$ x+ G  A2 R; O" G26
    7 [5 J. q4 }/ S27
    5 s) g* {1 |& o3 _, x, b28& h4 a0 I8 d- x* O7 o& Y
    29) O, B7 T, K/ x4 f; d/ V- m
    30- h5 Q% T- z; H# r
    318 d( m& \6 E- p
    【NOTE】datetime64[ns] 序列的极值与均值, ^3 C/ D% Y) `3 ?; x
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。( O+ d& C+ m( w
    2 \" m% l. m7 Q: |  L/ Q' W
    10.2.3 dt对象" s, m4 A: f, S3 E3 m+ q- w
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    # X( ]/ V6 b! X/ \4 u, b! p
    ) \& ?! w( [- s  h% M第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。" @- O7 ?# ~2 s
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    2 U, @) q* c  _0 o6 L  y) i! \% V6 m7 i8 Y
    s.dt.date2 J. n7 u& i% {- s/ I* Q( A
    Out[33]: ) _- V6 y0 U5 Y  S
    0    2020-01-01
    ( m8 Q3 l; M( n5 F+ H5 y# _1    2020-01-02
    # D! P0 @  Y6 Y8 I# @7 A" z2    2020-01-038 r2 D4 p  J5 J' r8 V  q
    dtype: object+ s( X% {& @3 M) m7 N2 @% j
    5 d  c* Z$ O  V8 z9 y: O8 h
    s.dt.time6 X) [- b" K* D! l# J0 B5 D
    Out[34]: 2 D9 A* P+ x/ C
    0    00:00:00
    " Z; p$ ]  Y& d( R1    00:00:00. p- k2 d% ]1 a/ T0 _( H, q7 a
    2    00:00:00& m) m8 e. u: U* [5 f6 P
    dtype: object
    " x* a& ^# t3 Q3 @4 r5 C
    6 [6 {4 S% ~0 f% W" y. [/ Y) ]* ls.dt.day, w8 G& P( O7 H& |
    Out[35]:
    5 L0 d$ c5 U' F3 h" \1 P6 O  I0    13 c- V4 {6 @7 \+ M; F
    1    23 ]. b- V& z' m
    2    35 v/ q% y) d# u- M* Q" Z
    dtype: int64
    % s' d9 J* [* |0 z# ]+ V; ]% i3 e; l- U5 {, a, o
    s.dt.daysinmonth+ H+ {3 u: [% f3 O. M
    Out[36]: : L. y& I  q; h- Z: O
    0    31
    1 S8 U8 `! m" Q0 }' y1    31
    9 _5 a: v0 o( k; k+ v) o' Z% S2    315 y- Z& _  [% b2 s2 a5 }
    dtype: int64$ i% p& e5 Q* l4 x, O. S( D) _% |
    % N3 T" i+ r7 W) w% q+ B4 g) F
    1
    , [* D* F. [' }2
    # |7 v1 w& z/ I# u3
    " @5 u' ?! I, M/ D4
    ) B  L* v. `( G3 W5
    2 c/ |3 O  r4 C5 D66 }8 e! P; l$ w
    7
    1 z% Q* n/ K# z2 ^8' j7 O' @' `7 n5 b- d
    95 {" f- T$ L0 [% a
    105 R1 H% ~3 p4 V, B$ L0 c" G
    11& |# q5 x, l, a' q! d* r0 |
    12
    3 q+ L8 Y& R, D, R" A13* S% _' R) ^1 m( e
    14; s6 |4 w# f8 v, W: k  P. p# n
    15
    * Z8 P: ~7 X7 R/ l6 k& z16
    9 E! d$ @5 f+ g2 ?7 O* y  [* W17
    ! H0 `/ v6 `, ?9 z9 P18
    6 L. P* E# }4 ^1 Y+ B) r7 ?19
    " P* ?& p( h. `% a: e; m- f203 M1 Q; R- H( x' }+ L  K
    21+ F# R( t4 M% ^
    22
    ( z0 u" B5 G) S% o23
    2 B/ S& p5 K8 d& y" F) i, }' }24
    3 |% y& \4 a2 o. y- N; m9 ~25( ]2 M# q% J' m! L2 p) x7 r2 {6 t) }
    26
    % t' x6 j" P  Z& O* ~272 P: f5 _) H/ g: ]+ x
    28
    $ V9 J0 G1 z: u9 }# o& m8 j- H29
    * `$ N, N, y5 p5 z5 y' @; V  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:# r) `# b) e6 N7 {2 U4 v
    ; o3 p8 e! x) P' c3 x2 h( q$ Q/ k. W9 G
    s.dt.dayofweek* w! r, U* N' n  q, I
    Out[37]:
    8 e1 Q) b, u4 q' a- d" Z0    2' q& x6 s3 P# t2 l& N
    1    3. n  u" Q# {5 O6 h
    2    4, a8 T9 [4 s1 o/ x" o( t
    dtype: int64
    0 v- n+ i  _# Z/ U7 n$ p
    $ m" m/ Z4 e( e- p% \s.dt.month_name()+ l8 O8 a( E& L
    Out[38]: & q' c2 i( B& C# I; d
    0    January; h3 H, E0 J) Z! |' }4 `( a
    1    January
    : J* D0 i* I* Z) D& _5 u1 _7 G2    January6 _0 T8 r; k6 n4 K+ O( f+ p
    dtype: object/ w1 o; g, i+ C' L" v0 t- m
    - f$ I7 E+ |5 J- Z4 B& D, @. Q$ W
    s.dt.day_name()) V$ [) x4 e% V
    Out[39]: / o$ B; f8 t! I/ Q9 M5 J8 t9 c
    0    Wednesday
    - ]- G' b' Z6 y' j6 O1 |) l( Y2 P1     Thursday2 t7 c3 t( x( ^! [
    2       Friday
    8 J& r( V' |" h, N6 o( V$ Z; Rdtype: object
    4 Z" H/ B: d" z9 w% {( i6 k4 s8 N* d- s
    1
    $ Z$ U, \! P3 [! a6 f4 K0 n22 Q# O- B% \! W/ k. C4 Z
    3" p5 d8 h' G4 X9 @* K& |
    4
    3 ~( Y; X* z7 b! z, |1 F5- k2 {, y& e: f) }
    6
    4 Z; P. f& P# Y" b4 s( Y  i5 w7; Q7 P: s" H, X- H: \* z1 l8 u
    8% O( p$ d* v1 q+ ?0 B
    9" s6 ]% q$ ]6 f+ S0 d4 f4 K/ g
    10$ \" N! J' C: X
    11
    % F5 h9 B2 X; V$ _* Z; Q5 }12% D9 C- F' a% t  d, T7 r& ~, t+ B
    13( W- f8 r' z0 L) h
    14- w3 ^2 M: f$ _* g. U2 J
    15
    4 e: Z( S  Q4 N5 P0 E+ t; [# O16
    - K4 I, `& e! w; }/ u, n- j17- e/ i# A3 u3 N& F
    18) P% F- A" @% N( i
    19
    1 d; D. ?  ]+ L20
    5 Y, b1 x) e$ j: U" W第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:5 C$ b& S& I& L" @, q) C
    s.dt.is_year_start # 还可选 is_quarter/month_start
    ' y8 R$ T9 `# g2 R; @Out[40]: # `: j; ]7 l  i' G  n3 ]* R9 ~; v
    0     True
    ) w9 ~. r( O9 c4 X  `1    False
    3 f+ I: t  i8 L& K  w8 V1 t! [% n2    False: H3 n$ H# I' U/ b1 A; U' U
    dtype: bool
    , \% b# ]( \2 L1 v
    # Y$ O8 A4 t# _! W# w( V/ |s.dt.is_year_end # 还可选 is_quarter/month_end. h( W' Q) u( x' `1 W( u
    Out[41]:
    ( Q$ l: N; _# I2 S% q7 [) ^0    False
    8 ?  E' j1 S8 ?5 N3 M% k6 x1    False0 r4 {. _0 h/ d  C! |; |
    2    False% [1 i- A  j$ ^2 j/ v5 o
    dtype: bool6 Y7 |7 e0 r  ?
    1
    ( J0 T4 |& n% s" ^2
    : E9 H7 ^% Z: `! L# b" G/ I& H% C& |3
    8 _  c* J$ O4 I4
    ! L, N. ~+ `+ @. }9 V/ ?6 r4 s  N/ P5
    ' p0 e' w, T- N* @0 z' h4 M5 U9 v6* ]* j* P( v# N+ Q5 |3 S8 h
    73 h: h9 n/ c  I8 e1 o
    8( E$ m- u  a' u- O, I# B; z
    9
    9 [2 S8 t& L" _10
    9 z- m" w7 J2 ?% x! v5 E119 A3 H5 g1 [5 [7 @
    12
    . v+ ]& e! c" P) {; o+ ~132 J% n8 J# P; l9 Z% S
    第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。5 a+ d" z) h1 @9 ~2 `( o
    s = pd.Series(pd.date_range('2020-1-1 20:35:00',2 G! C" x! z9 r; ~! h+ P7 f
                                '2020-1-1 22:35:00',1 s# l/ v5 c5 g1 [0 B% ]8 A
                                freq='45min'))
    ; u3 A7 f9 e2 C) t% N
    ; K; L! `5 R/ u' x9 l0 _2 d4 U# P/ ]
    * N( X7 T" j; Y; M" ~  fs) Y9 O1 t# P3 h" R; N  _9 T
    Out[43]:
    % m  A6 |. u: {# n0   2020-01-01 20:35:00
    * X9 q# N3 ]) h0 B! I7 L1   2020-01-01 21:20:00
    6 d2 F  K+ @/ P  M# \2   2020-01-01 22:05:00
    ) i: J2 ~7 L' I# ^dtype: datetime64[ns]
    ! S+ L- x& k2 x1 T; {9 l9 d. a3 l/ Q# `
    s.dt.round('1H')8 s( g" H5 W+ r! X) O, S
    Out[44]:
    ' ^2 z; h2 d" }# p0 Z; p1 e0   2020-01-01 21:00:00) H1 T" v+ k4 N
    1   2020-01-01 21:00:00
    4 U+ W% u: G: L$ q* `" g" L) U$ ?/ ]( E2   2020-01-01 22:00:00
    ! C$ t- Y: ~5 ?5 A; D/ K" Z, zdtype: datetime64[ns]" U6 O! m- I, A

    - `4 ^% g. W; {s.dt.ceil('1H')& L  P$ q( Z) a7 u( W3 |: X( C
    Out[45]: 7 w0 l7 `* x0 G7 V2 v
    0   2020-01-01 21:00:00
    + p( i' H3 ?: {' ?/ H4 e1   2020-01-01 22:00:002 ~  y0 [3 x: b* l- k
    2   2020-01-01 23:00:006 p5 T5 ]/ i; ^# [1 U1 Y5 N4 \
    dtype: datetime64[ns]
    ( k3 B; p' b% S$ U- D2 n  z  z+ t2 p
    s.dt.floor('1H')
    - d/ ~: t# q1 d6 N& v0 y- [' ^1 yOut[46]:
    / _* b1 {+ i+ F; U0   2020-01-01 20:00:00
    * l5 {+ t: w) S$ n- M1   2020-01-01 21:00:00
    , \! D. N& o  d2   2020-01-01 22:00:00- f1 r+ P+ m+ G' a$ ?. @
    dtype: datetime64[ns]
    # x7 v# _  y$ d  C/ J
    3 l& e& Q6 g9 v" Q4 z8 W2 W1
    - u- l# I, I: t& |: I) U; O3 q2& ^; [  h: M  l$ J9 B, W
    3" x/ q" @! E1 U/ `  M
    4
    / Y! o) `0 K: n7 G! Z7 }5
      D* ], _9 X; b4 l) Z6
    1 {3 A# S* C" V7 S+ D) d4 Z7 y* A8 y7
    7 a6 g( g4 x+ R5 t% t. a$ A8
    8 T: {; w' N0 p/ Q' ~" ?9" t/ i0 C; G  ^
    10
    8 f0 `* J; n' J6 Z+ ~0 G11
    : \" q6 c- w' Y12% O6 Q: \4 t: g% C) e
    13" v  M( p- R: ]9 ]; t
    14/ t- K, Z" B% u; l
    152 k- B  x8 t& r2 \
    16
    8 N, W; H6 c- X, ~17
    7 |; x% S: }8 m* K189 X5 N& A2 f6 N5 y% Y; s' P- z6 G
    19( g6 B* Z3 w+ |, r0 `  ^" ~1 Z( ?. m
    20
    $ E# L# N# G$ ~21
    9 _2 b' K2 D4 ^+ b22
    9 k3 S+ R  R7 E. U) |: T" m3 q23( {& K4 q1 A8 W; q, K
    24
    4 }2 M' v7 t# z5 R( b, ~8 O" Q25
    # z  u0 Q8 S1 e! K! v" s. W1 f261 L4 F2 x- K. E
    27' K" F4 S9 [1 L: [
    28
    ' P5 {# F6 a- p, S$ m" |4 H1 k( a290 m1 @4 m1 I4 m
    30. q  d! w- n6 V, E
    318 j( F0 R( ?  J
    323 ~' i' J: h2 ?$ c) [
    10.2.4 时间戳的切片与索引
    / G9 M( t' J% l  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
    / Z1 @! w! @$ s- Y; }9 z( ?+ n' z. c% X0 T% t
    利用dt对象和布尔条件联合使用
    $ a) ?) _4 S1 S  Y3 y' a/ v; j" }9 a利用切片,后者常用于连续时间戳。
      j; D  Y! I9 t! H, ks = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))4 T% ?* ]( i7 p
    idx = pd.Series(s.index).dt( d! ~7 ]5 X0 P
    s.head(); u+ a: r  i( x- g. n

    4 H, n; T5 F* h2020-01-01    0
    6 l- k3 [/ f- t2 y( u; @* R2020-01-02    16 x; l' F9 c7 q; B- Q( _
    2020-01-03    1
    4 G8 k) @+ Y# h" g, {2020-01-04    04 W3 u( m+ O) v7 Q/ w
    2020-01-05    0
    ; V  ^9 B3 A! \; F/ I! ^Freq: D, dtype: int326 v7 C: c1 r, ^# Q) t8 c5 k
    1) a1 h2 n" f. h$ A6 f
    22 D9 L2 z: Z5 m. h& e; Q( \( p' P3 ^
    30 a$ A/ x) d* B8 q3 a: J+ r( R
    4+ [) R+ Y8 v- y( U0 q7 X
    51 Z* F& a3 @& ~" N; J& ]5 }
    6
    / f: r/ N; W& `: Y7- I) D% K' ~9 A& ^6 [) S
    8% P6 g7 h. g$ M# Q
    9
    / O! ]4 ?9 _" }$ p3 t7 ]& P104 ?, R/ a6 G: A2 U
    Example1:每月的第一天或者最后一天( }6 z/ |* `' O6 `

    ' Z6 {' M0 d/ W5 M  z: v5 is[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
    7 q' Y" l1 q1 e4 I: _Out[50]: " }9 _9 l. r) _5 H- N7 E) m9 D
    2020-01-01    1
    $ a, l! P( z. b' @  w4 o0 e& q2020-01-31    0
    1 G4 f: l* U+ U: O' A8 {2020-02-01    1
    * G) P" m: i5 h0 R* y2020-02-29    1
    3 `* s/ `3 ]: l. n" E( r/ `4 Y2020-03-01    0
    - l9 q- ]+ V4 m* j3 {dtype: int32
    6 b: z! o. B8 A  l5 y% `2 V+ n0 I% {1
      P" a0 [0 D# z9 {2
    ( C+ B+ ?) B# H30 c# a& s' s" r* O( ]1 ]
    4
    2 I' k  F( `% h6 [1 X53 l8 O3 d" n- C$ w% Z
    64 |, ]  K6 Z7 \# [
    7
    8 a* Q  w9 d# P5 l8
    $ z+ t- i# i. z5 v, k8 \Example2:双休日
    ' _& U: Z* L' M8 T( G" c/ P. G* f8 L& I: h
    s[idx.dayofweek.isin([5,6]).values].head()/ ~# M% U/ M9 d: Z9 M3 {: w2 ]
    Out[51]:
    2 M9 U' t8 `& K$ ?& @) V6 G2020-01-04    16 r3 D" T9 D. b- ?  z) ]
    2020-01-05    0
    ) I: d1 p. @# O8 ^* S& H. }2020-01-11    08 G1 n& c  x) I' B4 m* }+ K
    2020-01-12    1
    * Q( [! Q9 h, L6 k  {, ]2 P. k2020-01-18    1. @: k. @: }. h) l$ D; K) D. J
    dtype: int322 I9 D2 X. Z0 e7 h$ c5 ~
    1
    ; K) Y+ m. K' B  h26 b. |+ R- {* w% J* j( m
    3
    % F: t2 W* W: d3 d. c1 t4
    3 A, e# p' }' p7 C& i2 ~- m5$ L: p; l, a2 j" E$ y8 ^# q
    6# }7 c3 ^* q. q' o; \1 h
    7/ [/ ]1 n* Z2 ]7 }% h
    8
    + N% ?0 E/ c) c0 }: b# }Example3:取出单日值
    - [; x4 K8 Q- q0 M
    ( U& @% p0 @6 zs['2020-01-01']8 ^$ ]8 S: b4 }- b3 \
    Out[52]: 1
    ' C( b) j/ y9 T6 k' E6 _+ G( m7 f6 q+ N" t
    s['20200101'] # 自动转换标准格式! N0 P4 `) `  }
    Out[53]: 1$ K# s5 D1 \2 d( P/ t) X
    15 U# |- K4 x3 E' a. y+ t: f
    2! ^7 I4 e- A5 D  Z  C
    3
    9 M" U' f6 A8 j) w5 H8 o4
    $ S1 t4 k, ]) K% L57 a# e# B+ @( N: q8 j( D+ P* h1 L# j
    Example4:取出七月+ }: b8 q% l5 r7 G7 U* @# N

    3 ]* ?" @8 }$ S. w7 ws['2020-07'].head()
    6 y1 F9 U0 Q/ g& H8 w, e4 \Out[54]: ' v& Y: d1 k! Z% j8 I5 S2 W3 [# c9 X
    2020-07-01    0  t! _% M. S: }
    2020-07-02    1  e' y( e: u) S
    2020-07-03    0
    / a  r% c* H: W$ y$ y4 w% _) f2020-07-04    0
    0 {7 _# ]9 H+ `; N6 [6 o2020-07-05    0
    ' \" ]7 |; \- [4 hFreq: D, dtype: int32
    " I  C3 n; v! Y1
    7 [- S2 ]: m" X' ~- L3 o. i24 U4 Y0 f) e2 z! Z$ n7 P. z
    3) g4 q5 \6 N( N8 f7 C6 ]/ ?, }
    4
    ' C+ X) M# u; n' h5" |3 N  T& s& U# H  |3 ^3 D
    6' F: r0 s# H- M% |2 G
    76 |* ^1 E6 w& r0 w- p" O
    8
    & j/ I4 k9 N6 u0 U8 K6 q) ]Example5:取出5月初至7月15日# B+ w/ s7 h  E* x% E4 I* L

    2 @% M# A8 Y1 x9 Y6 y' V& ]  b  ss['2020-05':'2020-7-15'].head()4 M. j. K2 ?( `% G3 G$ H
    Out[55]:
    7 z. \5 {- m6 y4 i2020-05-01    0" g* V' S7 F1 }# w! f, Q+ I
    2020-05-02    1
    - F' T4 ]# Q, I1 `2020-05-03    0( ^1 L* o3 G! V, N  d
    2020-05-04    1
    & a' K" t! R0 x& v/ s: F. Y5 \  {0 L2020-05-05    1
    ' T$ p+ E2 [/ s. _4 zFreq: D, dtype: int32
    - i. `) z& u7 \1 w/ R* m3 F2 v1 P' u" A( N0 i4 \
    s['2020-05':'2020-7-15'].tail()
    $ F+ ]/ ?: F+ p% W! K' q! b( {Out[56]: " R  s) G4 E9 R- s' c+ x
    2020-07-11    0
    . }/ f) a" F% f" m* M2020-07-12    0
    4 _8 e( J' l4 a2020-07-13    1
    % l! [* k2 Z/ ]7 L3 h( l2020-07-14    0
    " i7 D8 I; R. R3 k2020-07-15    1) G$ W2 y! j* @7 F9 X
    Freq: D, dtype: int32
    ) `, u# G# Y/ D+ B) D( G' q$ o- f: c0 e
    19 y' a5 n& M. H7 y  H) ~' Q$ Y% [1 r
    2
    / L: W8 X' r8 I8 h5 Z3
    9 ]* X1 x- F0 N6 B, @) a4
    : z* o9 `* j( d, D$ K5 G: E5( h& V7 N" P- S3 K8 j
    61 {* K3 t1 L( X7 b
    7
    ! p% r. M) A! {8 h8
    ! m- u; \! [% K$ ]+ n9
    - B; h  W* R) l$ b( a3 f10( N1 ]# ?& t5 l* D( @. {3 h
    11
    6 |+ X5 F5 u+ n. [+ J3 q( E) J9 Y# Q12
    5 K8 v: z3 Z4 ]6 ]/ y1 D7 ?13
    ; d! Y2 ~/ I3 N% F; m14
    : Y. W" d8 ^8 v' p* t5 U4 Q/ W% G154 F1 S) f- n# G3 ?
    16) Q% o  Y3 v1 n' C% W1 V
    17* K. S; ?( p6 F# u: b( q
    10.3 时间差
    $ D: A+ `8 U) w0 C7 J* x1 K' m10.3.1 Timedelta的生成
    2 `5 L# @3 g2 _+ Qpandas.Timedelta(value=<object object>, unit=None, **kwargs)* l+ N% [2 O" X' I5 x' l. k/ V, j
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。# G4 H) h* P; [, _
      可能的值有:
    % e* z1 h0 o. `  P! U
    ) t1 ?0 r# v& N7 q) P% N‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    9 X' z, v% P) x‘days’ or ‘day’' v0 K" x$ U$ \1 L
    ‘hours’, ‘hour’, ‘hr’, or ‘h’7 {7 `% o/ x9 P: m/ r, I7 @+ a
    ‘minutes’, ‘minute’, ‘min’, or ‘m’- z1 Z% N/ o* q6 F2 i4 H/ K. ?
    ‘seconds’, ‘second’, or ‘sec’
    1 _3 [5 x. x% d; L6 P7 Y# W9 u毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    % i! \' X: Y+ h微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    9 A5 b" W! D3 P/ ?2 c& S8 M* _纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.1 {& w3 |0 M9 n
    时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    - N/ l/ D. X/ n, Dpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    2 N% V" s, q% Y/ O9 YOut[57]: Timedelta('1 days 00:25:00')4 B  o( ?3 Y+ O2 }/ h

    # L$ D6 m5 G) Y! e$ P1 R! z# Mpd.Timedelta(days=1, minutes=25) # 需要注意加s! I/ H" J" W* Y% M" B
    Out[58]: Timedelta('1 days 00:25:00')& G1 Y5 q' W4 ?1 v* n! l
    4 c, @5 G% R( O0 U* [
    pd.Timedelta('1 days 25 minutes') # 字符串生成6 N3 X4 X2 ?- K8 j
    Out[59]: Timedelta('1 days 00:25:00')
    4 q/ e! X. W  ?* a" _
    , J( X8 \4 C$ ^8 H: e, Tpd.Timedelta(1, "d")2 U2 r$ e2 L. q/ G- N  g5 ]
    Out[58]: Timedelta('1 days 00:00:00')
    * D0 K# D6 Q  A6 ?# n, I8 G19 [- F0 a/ V1 B* I& ~4 ^- e
    2" Z! E; e* @, A) m( y8 X2 e  G
    32 {7 C1 M& w$ J# o/ @! }
    4
    ) |9 u  y7 A* k5
    & Y3 W  I$ y* n0 ~$ s! D  Y6, r( \' j1 n% W! s* ^
    7( I, J. [/ @  l- v% A
    82 Y, `, U3 f. N
    9
    - y" c. \0 S/ X* P* W) h108 G6 l* x: j+ _: c' t6 |0 V
    11
    & ~/ k6 }4 N1 a# M7 C2 v生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :" b/ o6 Q1 e. |# m
    s = pd.to_timedelta(df.Time_Record)) n5 M0 O6 D) `9 ^" x& n4 g

    & _, {8 P" |& G8 F4 Xs.head()
    $ p. i& T. h! t9 S  ]- d5 R3 H1 ^Out[61]:
    $ d! ^- y3 N  \: A7 y5 `6 p0   0 days 00:04:34
    3 H) Z# `2 o: D* r" e# }1   0 days 00:04:20
    . U. f+ ?  y' I5 I$ q2   0 days 00:05:22
    6 m. B. `" {$ ^3   0 days 00:04:08
    2 Q$ ~7 W) Z0 I+ J( `3 a( P4   0 days 00:05:22
      d' J  b" X& {  R6 G8 A& }- t$ IName: Time_Record, dtype: timedelta64[ns]. y8 @7 W6 v: E8 }( X& L3 n+ A
    1
    2 [# c* ]6 m# b% |9 E2
    : b2 Y. J9 [2 i% X% Z+ O, S3
    6 _" G" X$ ?5 D$ n9 w4# C% \8 V9 h' n! E# W0 X
    5
    / x- ?+ l$ z4 @- R% P( v  x6' @& E' t& a' w* b
    7
    * i0 F% M' B6 ~8 I: _8
    * s* U* Z, d: m- G, w# y9- K5 [6 g; Y! N: D( R
    10
    ( D7 P3 v# n; G& n5 e% i6 }/ ]7 W# A与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:6 e; V4 J2 ]- A0 E' w; C6 q
    pd.timedelta_range('0s', '1000s', freq='6min')- `% G3 A; X! `8 `1 b9 _
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')( Q6 N8 w: A# P' W. }* A; n

    2 ?6 R( ?$ D8 G- Tpd.timedelta_range('0s', '1000s', periods=3)# X! G9 g/ W& W
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    ' x, P7 B& O/ j4 f  T% w1" V/ F  ?! e9 U
    2
    ; ^8 w/ \; g3 R' {& ^! _8 N3/ H2 u4 ~) D; n$ L4 A
    4! j% j! u( j8 k; \- N! O) R) F
    5, v7 a: J+ g% c  D+ F6 i' s
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    ( z5 @. [# ~5 X5 Ds.dt.seconds.head()3 ^2 M& t6 ]; p4 I- c% @7 m/ @) \, C
    Out[64]:
    % J2 l' T3 t0 S8 |0    274
    3 u: u' y# V; f+ d( e# k. _1    260
    . j6 d6 [0 I" a' \/ R% v7 T5 @2    3223 Q& M* m2 q3 @/ C) H8 u0 h
    3    248
    / u  N6 Z- M3 I, @8 i3 i- [& F3 s& ?4    3229 t8 U3 k: ^# G  T1 z5 |9 z
    Name: Time_Record, dtype: int64
    7 c% E# Z5 x4 @& n& ^1
    % v" Q. ^/ o& Z. ]% v, A2* c1 L+ j5 z; `% I6 J
    3+ \9 g5 e0 [' K, I
    4
    : W/ V6 q, [3 J  ]$ h% v$ H& k5: z* ]' L7 `4 _2 I0 z
    6
    2 Z7 y6 V5 r' O* j0 j$ v, L# n# a73 F# s6 s* }. B
    8
    " j! X5 m+ n8 [7 e如果不想对天数取余而直接对应秒数,可以使用total_seconds. f. k' N. O. t; T7 O

    ! B2 v/ J! _# P1 F* l  d  a/ Hs.dt.total_seconds().head()
    % E/ a5 _# i* aOut[65]: 2 a) u, e2 n+ ^, E2 N# k2 v5 K
    0    274.0
    ( d! Z6 d' f5 {$ |' p1    260.0
    # V: ]2 N! E! @1 S9 `2    322.0
    - s& h* u3 F$ E1 V2 f7 o+ |3    248.0
    5 o% T) m" W# \: V4    322.0
    & N2 Q: r$ j4 YName: Time_Record, dtype: float643 o( H# E# M- G
    17 j) U* A) e- d
    2- h5 M' ]0 w$ T  p/ G* ?4 @8 t+ w# h* u/ X
    3
      Z& \' Y0 c, [( o: u1 i( Q5 ?4
    / z! H  y8 v8 [5
    ' d2 D  k+ ]$ x; E4 R; g6
    4 i! f! m2 p5 J' a8 ]2 S. b7. {# i% A) c8 h' R7 w5 D
    8/ t5 m( l1 k1 x+ `; h, [" i
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    $ \. e6 P  r3 r+ L- ?- \; N( o; R" V" I% j( I$ x
    pd.to_timedelta(df.Time_Record).dt.round('min').head()
    8 c+ l4 V4 _) V. b. z) F5 U; JOut[66]:
      d; a+ M6 _& G" ?1 e: N3 w0   0 days 00:05:00+ W. f' d- \3 T( S! w( x
    1   0 days 00:04:00
    % k7 R/ i8 b6 u7 J8 ]) W  Q2   0 days 00:05:00
    ' f6 z) X) k! b3   0 days 00:04:001 x& ?- }( u7 ?/ [* g1 a
    4   0 days 00:05:00
    5 L) p: T# N% l7 B& E/ YName: Time_Record, dtype: timedelta64[ns]
    4 b4 H3 _; o) i" s6 e* r- q! ^; h6 L  h1
    - s7 ]' a$ ]8 a9 L. T: ~2! q& ]# A$ {0 ~' L& c
    33 j& D' b2 J# C, t) m+ x/ j* s
    4
    4 U7 M* A, z5 }& L5) M* D# h, d$ |4 r9 Q, V
    6
    ( n* }: a' D; M5 y# f- W7# O; b( g8 j* i2 U4 H5 w6 a% u
    8
    8 u& [9 L. ?5 s: p2 |10.2.2 Timedelta的运算+ c/ A0 p  \9 p8 _0 P1 I. }
    单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
      `0 ?9 [, U% {  J* a6 t8 A4 qtd1 = pd.Timedelta(days=1), Y8 v6 f( m1 {- [
    td2 = pd.Timedelta(days=3)) |+ ?" S1 W  u/ G# y6 a; l2 l
    ts = pd.Timestamp('20200101')
    : C$ y' r( r1 Q4 W2 O* V3 [) b1 }7 o3 B9 r' s+ s5 I1 Z2 a3 \+ R
    td1 * 2. |: h! O6 @: p4 J2 Q% M+ D6 j& n
    Out[70]: Timedelta('2 days 00:00:00')% _# C1 \/ D- i# ?7 F5 `

    ; X* l3 d; y- C" n% L( G' Y, Gtd2 - td1
    & ]" |/ n' X9 F, @3 ]: T8 I# FOut[71]: Timedelta('2 days 00:00:00'); ^; p2 z% W2 Z! |) O' X  o) U
    * i- n  u/ K, C$ b
    ts + td1
    3 D  ^" \& s% f7 h% @2 rOut[72]: Timestamp('2020-01-02 00:00:00')$ U+ T8 v8 u2 q1 M5 S4 H1 J7 Q
    . q4 T2 h; M! L3 ]3 g+ j
    ts - td1
    & ~! X0 S' d* h/ w, [Out[73]: Timestamp('2019-12-31 00:00:00')
    0 R% |& e9 a. D9 n1
    1 e- E; q1 S- x9 z& @, k3 n23 P; q) R' O- I. G$ k
    3) ?  b) `. P8 o; t
    4. l3 t2 B, U+ b  h  c1 c
    52 P/ Y& ]% ]# B
    6
    ( s2 t! r% c' Y! R% u( D7
    1 u  u, J; b' w! e85 `  |# s/ _1 ?4 @% h) V7 D, s
    9
    , g+ z( [2 V& Z) {" ?10
    . P, N9 j+ Z& D- P' P' c  [11
    ; [2 r* ]9 \  a7 b) l5 R12
    4 f7 z7 r. u8 Z" X' M139 s% F! A; G9 f( Z' X- X" r
    14
    7 A7 y$ [* d% t* }6 z15% k5 @) i) I, W3 }" e5 {
    时间差的序列的运算,和上面方法相同:
    % v$ A6 o$ c: i" r, A4 w! S- @td1 = pd.timedelta_range(start='1 days', periods=5)
    % J  L; d) R1 R1 I. |# Itd2 = pd.timedelta_range(start='12 hours',
    + B1 i  d! k$ h2 ]! {                         freq='2H',
    4 U2 q6 A! m! ?9 Q8 X9 @                         periods=5)
    * O3 j: A" [; I9 u6 H. R0 nts = pd.date_range('20200101', '20200105')
    + k2 v% ~, c5 I: Ftd1,td2,ts5 ~; I9 o1 \7 M7 i  e8 L

    5 }( a) F0 r+ g& B& B2 J- b& hTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')& S, d9 }8 g/ ?& l
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',- k5 K3 @1 G# l" j: l4 v$ h* j5 ^
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
      ]) ^- E9 T  F( {DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    0 M/ L! H- i% c5 ^& L9 `               '2020-01-05'],1 N: O* v5 J1 y9 c: W: [
                  dtype='datetime64[ns]', freq='D')7 F% V) L+ J" h& |: a1 l
    1+ q: |* ?: G! T/ a% W: \# Z' e4 V
    26 k2 s; l! |+ j& z# T& H: E+ @
    3
    9 K2 `+ u; O. Z. X- C+ D6 Y4
    & Q+ z1 H: \# t' |56 V7 G/ D" C6 c
    6" n% O1 V2 t9 ~" a4 X, A9 K$ z0 X
    7
    8 n, N$ R+ ^( z, K8
    / ^+ U5 Z/ l/ s4 C9
    + s/ L& Y$ h, e- S$ `10
    # }  ^' f+ I6 `11
    3 Y/ h) T, ~# [( s! b+ \3 J12$ `) \- L& H  Y! n- Y
    13
    6 X( r8 y1 D2 z# D/ Ctd1 * 5, ^- J7 S' Q3 u! N8 N9 D% e
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')2 R# d$ ?9 A& L; ]# C9 n

    ' c: e; T7 f" B& htd1 * pd.Series(list(range(5))) # 逐个相乘
    . S) Q! q. b5 K0 yOut[78]:
    2 B. t0 h) B3 x. C2 K0    0 days" y4 n7 R) M( D
    1    2 days
    / j- Z: L$ u# z' L$ \6 O0 c+ f: c2    6 days
    ( O% E- |- U* b7 ?3   12 days
    + P) ]; x1 o$ {, x4   20 days% h" J/ M# J9 {
    dtype: timedelta64[ns]1 ?2 R: k1 E" \8 I
      o& q0 m0 X6 ?" P6 U
    td1 - td2
    , \- M/ d- _2 b+ jOut[79]:
    ! f& U  I  l5 {' s1 y. f3 V, ^, k0 ZTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',. J$ w' B# ^" N) ]2 t  I
                    '3 days 06:00:00', '4 days 04:00:00'],
    , x1 t* k) X; D% `' I" x               dtype='timedelta64[ns]', freq=None)
    ' ~8 q8 f) s9 j. q; I+ e/ ~0 D2 {" ]) q: C) i6 U2 a  q
    td1 + pd.Timestamp('20200101')+ g$ \- h) w5 |' _% l( b
    Out[80]: % }: S. ]: T# u+ E4 K
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    4 x# U; f( p/ q- J- g/ `0 c5 g               '2020-01-06'],dtype='datetime64[ns]', freq='D')0 z5 z" \9 m/ S% s

    * |9 V& H9 p9 H- htd1 + ts # 逐个相加
    ! \6 D9 [7 y. E& j; j! A. A5 Y4 nOut[81]:
    7 w3 K9 x, F9 v# E0 P1 X& T( bDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08'," \  B% _/ }# ~6 ^: m
                   '2020-01-10'],
    ' d& _* `8 y0 N- z6 F  K+ R  X9 l              dtype='datetime64[ns]', freq=None)
    9 j) m2 A( K' w- E* a! q6 Z) h0 [( ]4 v: D4 s  `
    1& W3 V% `3 d3 g
    2
    ; Y- C, P, ~) A3 s& d+ L, G$ H3
    8 r' o4 x& P2 M: ^2 W& y6 t* G6 V4
    ( j" ]# Y7 B" P7 n# R, S5
    : D, O) M6 f# S+ F6
    6 z6 C2 L, I- C8 B3 F7
    1 E2 U) m; y5 u" [, u3 p8
    6 f) {1 G* ?9 {! \9
    3 y, P4 g  a2 x& x( ^* W3 N10
    . ^( e* X5 i5 q! S4 U/ m! ~11/ f+ D8 N; G$ S2 j. R4 ?3 {8 t5 P
    12
    ( x+ A0 F4 n  v13
    , z  I+ B3 i: L& }14
    ' s# }4 x" u* W( g# `. Z2 {15
    / q  x0 S/ t3 x$ j  x4 o16  N7 K6 q' F) U
    17) l& E: H2 r0 R3 L# c5 y
    18
    - F+ G% P: B7 q  _192 @. Q' w# f& w
    20% d& E8 R. t3 y& Y
    215 _9 ?0 K' O- C+ y/ G
    22/ ]; ?$ C1 f2 p. X6 X5 [" J$ o
    235 u  u1 Z! b1 M9 H3 j7 V
    24# N( z2 e% Z) `, E, i+ ~
    25
    7 T3 f6 i6 z5 t+ \7 ^  n26" }2 M8 ]. S1 Q# p! Q
    271 }6 L1 Y+ z1 a6 Z
    28
    0 B+ q, B% k3 _3 r* R1 S10.4 日期偏置
    3 x  ]/ N% B9 `7 w10.4.1 Offset对象# z5 q# Q/ }7 f9 e3 @
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。, r* ^0 y( a3 n+ z7 Q+ X

    3 i% m% T3 V. t. ?! IDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:, A4 J- V% @( U& ~! R

    / O* ~' T0 J. j8 Ds.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    4 r; J# T* T# q* |" Ms.kwds:{‘week’: 0, ‘weekday’: 0}9 a% N  r7 w/ k  Z; D( F2 b5 `
    s.wek/s.weekday:顾名思义8 M5 D% U1 b. @* k/ ]
    有14个方法,包括:: ~- G; W# b- Y8 }: N# V$ r4 \; q

    ) ^* ?4 @0 z; Y' f/ JDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。0 m' L- t# i4 H" }8 a6 C
    pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
    9 c8 t+ ~- i% E3 Z% h" N2 K. O
    " F: Y. c  X3 B* L- v: H有两个参数:/ U8 m2 r6 |$ j& A
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。* I2 r' J, ^, G/ H9 ~; C
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    2 ?$ o+ j$ c  p' L. _2 I" mpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。$ N* P- ?" p; ?7 b$ j
    % U3 O% G0 L0 @4 T" c, w
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    $ Z! l; j  Y: x/ M% q: Q, hOut[82]: Timestamp('2020-09-07 00:00:00'). C. I( {+ L; w2 G6 ~2 j8 q- b
    , p# J& g" q% K) i6 M. Q2 M
    pd.Timestamp('20200907') + pd.offsets.BDay(30)
    ; e% ~/ w+ O! y3 MOut[83]: Timestamp('2020-10-19 00:00:00')
    ' a9 Q) m& p, B  H& E% y* C; ?1& s6 {& ^! ], ]5 ^  [# d& ^
    22 h+ w- W' L+ H; |4 k1 Z' \
    3, \' j1 w( M% s. T4 a4 f3 J
    4( \  i& M* k& {' t7 n9 x! s
    5, c+ F- }+ \; g& e0 k/ w+ ~3 `
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:* E& n' q6 h7 l# m0 L& K4 q" Q& i
    2 F5 s4 v# C( Q5 b
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0); y1 g4 |& q* h1 F, E
    Out[84]: Timestamp('2020-08-03 00:00:00')
    0 d- k/ s! p0 Q3 y
    # b4 Y7 q  E" H$ g- U9 S2 p7 Xpd.Timestamp('20200907') - pd.offsets.BDay(30)6 U( Z7 z( t8 J$ X
    Out[85]: Timestamp('2020-07-27 00:00:00')8 b" l: P/ k6 i; `  X
      y# O! j0 x  y
    pd.Timestamp('20200907') + pd.offsets.MonthEnd()
    : l  E, f- O  h+ G4 ^2 HOut[86]: Timestamp('2020-09-30 00:00:00')
    $ k& j* l4 x( V; N- Q1* P. U( e& l% V$ K6 q( Y/ N
    2- D. U: r7 \) J9 M- X
    3
    + l, J- I1 N7 ^; C! m  t) k4
    / x/ _3 N- `9 M) s# }5
    / ~, }! n  x& Q. Z# n6
    1 f, r8 J  w, l6 R( s7' v. _' p) S* `3 `% [. N
    88 S! h7 ]) M& j4 z2 F% P% o
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。) y" \/ a0 K: f$ F8 \) ^' A( z
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:1 y# a* J" R5 ^8 y4 X: V. P
    9 A; X1 J3 u% F
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])5 ?' i' E3 X/ h9 O6 C" r
    dr = pd.date_range('20200108', '20200111'): Z9 V& d& l& H0 g/ @0 n
    . N) [7 @& i+ |3 A# U1 ~! w" Q
    dr.to_series().dt.dayofweek8 v% s/ r+ G+ {/ ]1 K# v3 H& \1 @
    Out[89]: # I3 M8 C8 e# {1 L, m$ |  }
    2020-01-08    2
    1 Q: ~# M" i+ r+ C5 v2020-01-09    37 U( k0 a5 D1 I" W  E0 q1 s* m0 K
    2020-01-10    4
    5 `" c% Y/ Y' i; L7 S" o4 z3 u2020-01-11    5
    : Y4 a6 T1 V2 `0 y2 ?; DFreq: D, dtype: int64# _: I  u+ x/ Q1 Y4 z9 |) p) a

    - c! _+ P. ^, F) G( q, Q7 l[i + my_filter for i in dr]: C9 u+ E# B1 |9 @  P# z  O
    Out[90]: ! l$ N* V5 Y2 T$ Y" b
    [Timestamp('2020-01-10 00:00:00'),
    9 c7 r9 {% \/ p) J+ i" p3 G Timestamp('2020-01-10 00:00:00'),. a/ K8 m- G  Z' S2 S- c% D
    Timestamp('2020-01-15 00:00:00'),( D0 p* j9 M# L$ D
    Timestamp('2020-01-15 00:00:00')]
    + W  F( I2 |2 c  G2 x
    7 J" z( p4 [8 s2 S* L# k1
    + z/ [" i2 q: I6 g8 ~3 v% x, \* f2
    9 B  {. c3 [' Q3$ |5 [# f- M" ?& Q
    4- J1 ]& d& L* s- O2 b
    5
    + w+ n7 H: n6 E9 C6
    0 Z7 B: Z9 i& W0 _* M77 S2 I- [/ b& k& f3 ~
    8
    # a, b5 G+ R  ]& ~9
    " b# Y, w7 U% {10
    ) r7 R0 U; U6 ]. S+ n; M% U11  Z$ X5 h5 s4 a9 q' p
    12
    " m7 F, C0 c9 Y3 p' M7 U- o/ F4 R" R131 l0 F" Y$ n, {: @* W3 G. y* C
    14! d: e! ~5 b$ \7 x% H
    15
    $ h/ \4 P( c. {$ }( F; d) C) h160 B- b( u3 L! k9 i- |% m& c
    17
    2 r9 O/ ?- }& F$ V7 ^  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
    ' a+ h7 k3 ?: G) Z- T% Z3 E- {# @9 Z; v: ]  o
    【CAUTION】不要使用部分Offset0 S8 ~/ w' Y- [# g+ h& j3 ?' R+ r4 A
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
    , E* w7 [& p  H. k5 l8 C% ?3 d6 C9 {4 ]6 @( \
    10.4.2 偏置字符串) j8 A$ @* _/ S$ T
      前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。1 C6 G: \: B0 r7 n) f

    , E1 L0 Y" h0 }$ Y; M' G  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。" b) W3 e1 E) W0 ]+ x% c

    & G% x  |! J1 Y. ]% epd.date_range('20200101','20200331', freq='MS') # 月初- O$ k4 ?' m8 c8 A! i; ^1 ]
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'). O* p5 T& K6 ?1 c! C
    4 l( \2 \3 f0 G* X4 X3 Y6 V
    pd.date_range('20200101','20200331', freq='M') # 月末3 Z7 i- L) S7 a% o6 u0 W5 D
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    7 J( M# }7 K2 }5 x9 E+ k( ^8 t; t7 ^! O* T+ W! ~! Q
    pd.date_range('20200101','20200110', freq='B') # 工作日$ g7 f3 R+ d0 d4 e
    Out[93]: 9 G4 Z$ \% x+ I- ^) G
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',9 T" j* _3 D  @3 T  `5 E9 a/ P
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    ; Z, E; D0 O1 `) n              dtype='datetime64[ns]', freq='B')
    7 n9 ?7 b: h: d
    $ I1 h2 x$ B1 a" y/ {7 m# `+ F1 zpd.date_range('20200101','20200201', freq='W-MON') # 周一
    0 x1 B& Z" @. R! tOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON'): {% ^2 y/ C6 l- k- {
    + h# y" d( u6 Q1 d6 R* i
    pd.date_range('20200101','20200201',; n, \. b) X# {% W+ \
                  freq='WOM-1MON') # 每月第一个周一
    / Y+ K! x2 A0 Z
    / `" B) `/ N4 h% e$ B8 u8 R5 OOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    " y( {, {2 \: w! v: l" h1 U- {% t, e) B" b) @" L+ M$ V
    1: U" @# E5 ]: Q5 f" g
    2
    " h( p3 v. H# G7 c- I3
      T# v% _! m: \4! H! E1 ?! @  M# T7 ^7 Z0 _
    5' b2 i0 D" K" |5 Q* i/ P
    6
    6 S4 i. G# `8 X  [3 [7) L" a8 t" Y9 {* M: F+ k0 ]
    8# m; s, r) f$ ^: _' q* D, F% I$ t! p
    9
    7 D3 d, s* ~. E8 H; B. v% m10
      g/ L4 M2 k) O/ ~( R* q4 J! O! H2 p11
    2 U  W, J8 R6 J- z12% f+ y) |6 Q# S& Q* {8 J
    133 s! ^% n) t% \0 N* U7 O: `, w8 r! H9 W
    14
    2 R1 a5 s1 Z) u6 _& d6 n15
    1 L' K. R+ A3 ?16
    . X$ B2 ^2 Q8 r2 n- @/ M  j17
    1 v8 Y, z& s, ~+ m18. Q9 H" p) I3 W) I
    19
    2 _, A  G; \# b$ P上面的这些字符串,等价于使用如下的 Offset 对象:
    % d) R. ^8 t& k% m+ k8 D4 F. q+ o! c+ K' D7 n- E' U, B+ B" B
    pd.date_range('20200101','20200331',
    # x& C% @: {, K, e- E- b5 o              freq=pd.offsets.MonthBegin())
    * |* ], Y7 O3 P. s# C, S8 \
    & k- g: z/ j$ \1 M1 {Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'), O6 @  I8 E: l$ d# E* i
    ) L: Z0 \+ Z  Y" t& E
    pd.date_range('20200101','20200331',3 q+ q$ ]3 h/ p2 K7 Z
                  freq=pd.offsets.MonthEnd())9 l3 D( p. ?* H. p

    ' t2 O: p* k- I4 i6 x$ bOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')6 h4 P  {) u( C: h' `: w

    % O- [" O  `$ A4 n/ C, xpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    - y  H# a% Z- U1 ^Out[98]:
    3 a# }( z$ j& ^1 K: u( q; a# B: bDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',7 ^% p, E% l1 C3 U
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    1 u# r/ `/ N/ ]+ @% l3 P/ s- C              dtype='datetime64[ns]', freq='B')9 l3 H' n' G# w3 P# j

    9 B$ W# V  e% R. m( `9 Lpd.date_range('20200101','20200201',
    ( h" s3 l" U9 Q  b# P$ j+ B              freq=pd.offsets.CDay(weekmask='Mon'))  @/ J9 h! g8 P! ~
    5 Q: x) N0 z! }: ]( e5 B9 Q% L
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    ; T0 Y* f, C" t) z( q  c& Y* i, k
    pd.date_range('20200101','20200201',7 R& k9 D6 Q! w8 r: Q' l) e4 I) j
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    6 u* o* F9 U% y9 h) k. Y% B7 a# H, _2 Z1 x$ y5 u
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    3 e0 _# v& l2 o! H) V2 F; V1 v! d+ q4 k2 Q' U1 e; S1 |6 k
    1
    " B4 Q8 [0 b: P2- D- H7 f/ P) N8 `* Q
    3. T4 ]: Q$ a! d' \5 R& U) x/ j: a1 M
    4
    % q- O# ~4 n4 {3 ~' p5
    : O6 |3 ^3 n: Q# A1 A+ B9 d3 F, Y5 _6
    ' \" D& a) D' Q! v9 l/ N% X7
    % C/ x" R2 G" U6 ^8 j/ [9 [8+ y0 U/ [- N, Y$ I+ k7 `
    9
    . _- C& Y  \- z  x$ j. i10
    : r' I3 S( E# ~' \1 H. ^% D11
    2 s7 {" g, ?7 w2 B. ?+ U' q2 N12# `1 P* }) T$ x4 Y( \
    13  b" N3 w4 Y. S- D* h5 N4 n
    14) l( E2 X3 x( O& m0 m% o
    15) z" g0 S( g$ ~# E0 P, E) j- L% R
    16
    - J8 I# b5 y2 k! I  e177 a+ R' C, W  n9 O9 y
    18
    4 N) A' V# T/ i% q19
    : ^9 V! A) c5 O% D& l  M20& L. `7 E( ?3 p0 D3 ^2 G8 U  v
    21
    , b3 q5 o5 S$ `4 _6 o221 W1 D0 \- g9 g) q4 b0 n" U* T9 F# b; X! E
    23
    ; Z6 ~5 `" N( C1 f6 s! B" I8 R24
    $ d5 C7 m2 g2 {- V0 ^254 I) J/ ~( _1 x. B" Q! N$ K
    【CAUTION】关于时区问题的说明
    6 v6 g- `7 k  L, X* a) Q  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。' C6 l& W# I: `& p
    6 u( Z- f4 M5 |7 N5 V% q
    10.5、时序中的滑窗与分组0 [+ \, J9 \) a! n/ _9 O
    10.5.1 滑动窗口' q' @3 j" a' S, k9 V( J% V' N
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    5 E; H+ \7 T$ o, W0 r4 ~" o% H4 z% R' o) |! T+ x) k  |2 p
    import matplotlib.pyplot as plt. Q# g. s" @0 [" L  q
    idx = pd.date_range('20200101', '20201231', freq='B'). s; o( _4 h/ d% S3 {6 {6 a" p
    np.random.seed(2020)2 o& K( W) \3 D
    $ G2 ?3 B$ @" Q, [& L5 q, K
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加+ _3 {1 L6 e3 g- U$ M2 k
    s = pd.Series(data,index=idx), j) g1 F$ M5 ~7 w8 m
    s.head()
    1 }+ R( n. X$ f) zOut[106]:
    ; ], {- x( Y6 Q0 H8 B. x3 ?2020-01-01   -11 B0 b3 [: a3 t( n6 d
    2020-01-02   -2: @0 _% O9 K- N5 T. j0 \
    2020-01-03   -1$ r& p6 V* z1 V' x) ^7 {) o
    2020-01-06   -19 R; U$ v" m6 a; w: N% |
    2020-01-07   -2
    / g. V( _2 W/ A( v# P( |$ O% o3 IFreq: B, dtype: int32
    1 u# ]& r0 K" @+ ]" ?+ z; br = s.rolling('30D')# rolling可以指定freq或者offset对象; ^5 O! z4 ?6 Y+ p" v% q
    * `% P  _7 _  I
    plt.plot(s) # 蓝色线
    3 x9 q* ]2 R7 w+ c+ \Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]' O9 T7 |& v$ W7 @6 q7 J
    plt.title('BOLL LINES')# w" e, i, @- }. [4 J. T7 L& g
    Out[109]: Text(0.5, 1.0, 'BOLL LINES')
    9 u3 l4 o7 `* F  O) ], [
    4 x- ~) y# j; c& ]plt.plot(r.mean()) #橙色线6 T& X/ O8 k% F- x* ~
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    0 _0 |. c, Q7 x( K  m
    " ?, G% N. p: V6 Hplt.plot(r.mean()+r.std()*2) # 绿色线
    ; [, ~5 A  F: W9 l: S" HOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    2 g/ h# H9 A0 ?5 J2 P& H2 @! n# _2 g( D- r
    plt.plot(r.mean()-r.std()*2) # 红色线' A' w: z/ O) P! S3 j
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    # F0 x+ T* ~5 L. N& b7 f: \
    - {' W; {9 h* ~9 _, O6 K1
    4 ~9 m' i0 [8 `; k. n: C2
    & _/ Z& q& X8 c) J2 g6 r- W% ]# N3) R; v. R: ?: [- \) q+ }/ a- X
    4
    9 T3 u3 ~/ g  z4 o2 U" y' X54 V  N7 x7 z  c
    61 r8 L* v+ c' O- I/ \
    7
    ' ^! x2 _1 ]3 D$ X0 @8
      Z5 L. V0 F7 {/ U+ A0 b9. X$ M9 q/ w, i6 x& d
    103 A" I( ^- E" W) K" a! I: U3 S- G- q
    11
    " x% @$ {# [8 ^# f  q( N' n12) ], k, ~" O6 f6 \8 q& ~
    13( N$ d% u1 s: E1 f/ Y
    14
      [, ?1 z( \9 y, t15
    ' Z8 g: q& a  X; E16+ b9 ?, v9 W1 m, l. r
    17
    ! }8 i! u* s' X2 v$ t& v18
    . r$ I9 U6 r; R19
    4 A! P) G" V0 a. u; g1 x5 k0 v20
    1 {! F; i# p& Y1 N! J21
    : A* i& I% Q( w22
    0 {' [# I1 X) ?; _# x, e# U23
      ]! k- C  x5 s; Q, h24; a7 I4 x& }. ]3 y, Q/ b
    25- E  c) i$ U0 M1 o9 o+ H! p) g
    263 Z% w/ ~" }2 a/ a7 A
    27
    - W4 m* M7 f# s3 }. v28
    $ [& B7 X' X. M% U, b+ S291 Z+ {% q2 x4 U9 {" u! q
    8 q5 I$ v2 Q2 T
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。; s; Q; a4 |1 e- x
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。5 F, T( H& ?. c8 C( K" ~

    & C; w5 J9 w: x- q: }9 Kselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]1 s% o3 x+ H* h$ k( C6 x6 G
    bday_sum=select_bday.rolling(7,min_periods=1).sum()% o) N7 D: ]/ o8 l' d9 F
    result=bday_sum.reindex().ffill()# G/ s$ W1 I6 o4 u- D
    result
    4 O. p8 X5 M: N
    + ^) k( r$ j. L2020-01-01     -1.0
    0 d  g. g% L5 ?9 _/ \2020-01-02     -3.0
    ' I4 o: N# e% k/ z2020-01-03     -4.0
    , P% s# D9 t6 d& V% S. h9 f2020-01-06     -5.03 R: J3 W" `& `4 {
    2020-01-07     -7.0! s) n3 z6 l" f6 v9 @# ^
                  ...  
    - i# R2 e% k& J1 g3 G/ F2020-12-25    136.04 `0 A- Q$ O6 E5 c9 F1 S
    2020-12-28    133.09 v2 n; \8 q( L& E4 Y
    2020-12-29    131.0- h  d7 K- b% u$ ^
    2020-12-30    130.0# t$ P3 E- v# d' W
    2020-12-31    128.0# _0 ?+ ~# [/ a6 j% i) {
    Freq: B, Length: 262, dtype: float64
    3 N0 w; w1 R  w! B$ l. }3 m! G* N& X# A& S: V9 D! O
    1! O  D3 u1 H4 f$ _: C/ ~6 \
    2: Q; b. Z4 z/ Z+ r5 C/ i% a& }
    3. o, o* w  S$ K/ z4 x7 z: E
    4
    , L4 G) `' h$ ~) H# ]1 e5
    * Q& w. e9 l. a" I8 [% q- U67 q: M; t% G9 g4 l
    79 a0 q6 z5 \, p, C
    8
    ' t4 `; b# O6 x3 w9 A+ C: `9% ^' I- r$ H$ A7 A  f& i- @& O
    10( D9 ^0 \0 m/ M6 ~9 q
    11* I: o0 m8 H" a
    12- a) ?/ ?1 p7 _' O/ e" r$ {
    13" ?$ f( W) i4 r$ H$ _# Z+ W
    14
    7 c& Q7 H+ H: |+ ~  B15
    ' Z- i5 i/ |- O, E0 M+ G/ M% Y16
    ; t3 u; g! j/ D( Q2 J* [17
    4 x& W) {, c' U  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
      ]/ O# s! @+ a! w! r: b' }' G: Y& p  Y% ~1 _
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:' W; G8 z4 U. B1 v! j

    1 S+ K" t3 u4 L5 [0 g0 A9 hs.shift(freq='50D').head()" v1 e1 G" q, _/ ^+ v8 r0 M6 p
    Out[113]: 6 d  p! H/ Z! ^- D* `
    2020-02-20   -10 q. Y. ~$ {3 g$ ^" T' r& K
    2020-02-21   -2( L2 A2 t  w6 E  c) y* Y, g( @% o
    2020-02-22   -1
    3 |4 T! I5 B3 F$ k2020-02-25   -1" l# j6 b- \) j0 H+ E
    2020-02-26   -2
    $ d' ^3 g, u. \dtype: int32& K; g! x( [3 L! k
    11 P& l/ J- w6 u7 b. i* N9 X3 O$ J
    2( x4 D# y8 x$ n
    3
    7 O/ M$ s4 }% M- ?  d* ?1 S4
    $ j- }5 |1 c( Z" `) v/ k5' t3 K8 `& d2 s8 O, {  n
    6* w! J  R3 R9 l  }
    7% U7 j* p" \& f0 z1 R% }+ d) t
    8# ?' O6 C8 O) B# M* M% O8 L0 f! o9 }; U
      另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    # ~! N' n5 P' l2 f. S5 A" N* I- d/ B7 O- T* P1 ^
    my_series = pd.Series(s.index)
    2 N& u: K+ z; S) hmy_series.head(). k, [* ?) B* p
    Out[115]: . T3 z. t1 u5 f% E2 w  v
    0   2020-01-016 J+ f( f0 E. v( S
    1   2020-01-02+ n4 B6 j; N% `
    2   2020-01-03$ K- U9 w4 \8 B
    3   2020-01-06' k" T9 s; o" `
    4   2020-01-07
    ' P1 ~! ?5 o3 e$ I% h1 R6 xdtype: datetime64[ns]
    1 i: P1 ]7 i1 ^. l. D+ f, `" c, R, r& Z, P
    my_series.diff(1).head()8 m& [2 C9 ?# Z0 z) M
    Out[116]:
    % l& e" G1 @& a1 d5 Z+ [9 H0      NaT  R" W4 A8 ]! L- u' T7 k
    1   1 days  Z# j: x5 v* X
    2   1 days
    . m! Q7 L$ \& P8 c( J0 w3   3 days
    1 W- D* O" B' `4   1 days% d! G6 c+ r3 E/ m8 _& c1 c4 ?& P2 a
    dtype: timedelta64[ns]
    ) x' I( Q' f  c. Z5 o% S8 B
    2 O" U) a. `& N- `1( f$ J& e9 Y% B5 H
    2
    ) `! F; q0 D% G$ G3- d7 c7 {6 t$ j+ l& i
    4) u3 |: M, _% M& z6 S
    5( o* \$ H% V* ]9 p
    6
    * j8 m& f7 s) O7
    " j( K. i2 ^% e: Z4 b/ J) @8' D4 |+ ], S7 M" Z: c$ E
    9
    3 s6 W2 p( i' {+ ]103 x* K. A" `1 ~" |7 F
    11& X5 }& b  H, ^" `
    12
      M8 K$ E" o+ f0 J13
    ' V, b4 W+ X1 h& Y& N14
    7 i& L9 u4 _$ Y( s8 v! b159 s" K# V! D% Y# s' u! I& n
    16+ D0 m6 @2 u  Z- |* k
    178 c) q$ F. k% y; B2 f; o  y' p
    18& h6 o4 L! ]8 ~9 e* V
    10.5.2 重采样
    6 a' Q9 N- }2 C8 o" p/ y+ U  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)
    ) w7 D) M& |6 C常用参数有:$ {3 k, ?. H& ^2 Y1 v; T& S

    % M& H2 j- z& B2 `. \, u0 I- ]% |rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象5 a5 N( j* g3 S8 L
    axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    # A! b9 u- d7 ]5 C; Fclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    + a; i! z; a; K# b: u/ vlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。- {: L4 @* W; J+ J( G) v5 `& y
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    " j+ Q! p7 |/ f* W; b$ Ron:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。+ H2 a# B5 Q  o6 ?3 B8 d1 f. b
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。7 G. w( m5 K: o& @4 e
    origin参数有5种取值:
      ]5 c* v# f' J7 `% i/ }‘epoch’:从 1970-01-01开始算起, f( B9 i4 d  R( Q- Q
    ‘start’:原点是时间序列的第一个值8 U, W# @' V( H/ U  u9 V9 r4 R
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。& ~3 _/ }' C) t! v6 P2 q0 o  y( z
    'end':原点是时间序列的最后一个值(1.3.0版本才有)
    / J. m' @) f  P‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    / L# v7 U$ b7 E+ |# `offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。, G. m- V; }2 a. U8 S' x
      closed和计算有关,label和显示有关,closed才有开闭。
    , i$ r. p) R4 m% Z1 C# n0 p& P  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。! M% \" |$ Y9 {- E& K9 x% _1 V+ ?2 ^

    $ k2 f' T4 C+ ~" B: y7 T重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
    " M, b4 [  y% B: i* Y4 xs.resample('10D').mean().head()
    - L( w6 k5 x4 m0 I2 d4 |; Q* @! QOut[117]:
    $ N( J5 g+ d* |' v1 x2 K2020-01-01   -2.000000  q" F2 ^/ H6 C3 D7 j2 |- j/ n
    2020-01-11   -3.166667
    : e+ Z% y& k# T  w2020-01-21   -3.625000
    ' l* x9 k  F2 ]: t& p% N2020-01-31   -4.000000
    - g4 ]+ ]+ W' T3 Q# c2020-02-10   -0.375000
    - F; M6 C0 m3 L4 v' NFreq: 10D, dtype: float64
      q3 k( X! z+ `! [. U4 g, Z1
    8 X- g9 n7 n4 W  h26 M/ ?% [9 `1 Z
    30 y' N) S6 T# K4 ~& k  X: N
    4
    2 @/ L: n, Z' ?& k# U8 d' U3 F5
    4 I) d/ ]: F1 c- f+ G$ X6 {6
    6 h; y, w% {! r% P7; K3 g, ?. _4 U4 X  [, m
    8
    * I/ p" ]& r: r4 X, V) q. h可以通过apply方法自定义处理函数:
    ! P- _+ M9 a$ ~4 T0 E' O7 e; Ws.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差" R% h! x! [, ~# i

    0 ^6 \' [/ V0 P5 Z7 JOut[118]: - j7 o2 B1 b! i7 _6 S% b( Z7 m; }
    2020-01-01    30 W% ], }- K, A2 U
    2020-01-11    4
    ' Y$ N! Z+ f" p6 d1 S9 Z2020-01-21    4
    ) I% M4 A; r* F2 N6 B9 j2020-01-31    2$ w' Z6 y* Q, O3 ~+ k
    2020-02-10    4, Y* d( p3 l  e; ~8 k6 B+ L
    Freq: 10D, dtype: int32
    , v2 m, B/ T& b. e, S/ b/ |1
    ! O0 z3 J9 `4 J% G2 O" w& A! ^; E" ~2! o$ f9 @" P2 V( a$ a
    3% O( R8 M5 M: T7 W+ N: P8 V
    4
    ; J( I" n8 ?% M; d5
    5 Y+ o; ?" Q, D. D# e6
    - M4 X" X3 H% ?' C3 q, q7
    , O; a, ]7 s( T+ {8! Z! @, [- U+ E% ~; M- L
    9
    * g3 U9 q( a# o/ F& _1 Q  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    4 H2 v) D1 V& [! T" [' T6 a7 B) ~: Q2 x# {
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')4 k6 m/ w0 O7 |
    data = np.random.randint(-1,2,len(idx)).cumsum()
    ; m0 E! Z% _2 l+ J) m: Ms = pd.Series(data,index=idx)
    * @: _2 K3 T8 D9 is.head(): |' o+ q6 P& b0 E% a0 U$ c

    + v6 @/ A7 @5 E2 ~# U3 nOut[122]:
    : [% Q8 J, h# o" ~) j2020-01-01 08:26:35   -1" y( m. s. I: s
    2020-01-01 08:27:52   -1! p' j% d, m4 ?$ s: o# D( k
    2020-01-01 08:29:09   -2  C- {, G% q0 T+ z" H1 P) |
    2020-01-01 08:30:26   -30 @* J5 ~* g% m: V3 m. h
    2020-01-01 08:31:43   -4+ M0 L; _1 `5 l' T
    Freq: 77S, dtype: int32/ O: U% |! A- p9 H$ `7 P" V
    1
    " t/ `" \+ Q4 _/ s, @" A4 @) Q2
    ( ?, u) f3 {. S( h3
    1 C* E! d6 e$ {2 ?: D42 a/ q- |- ]) ~# |7 D0 {
    5
    3 c% e3 {  d  D9 M4 |8 F: k6
    1 u4 E8 c2 l" [& L6 _3 m7 s4 `# `' q7
    + D' v! t9 M- |6 c! E8
    1 K1 i8 i5 g! o9 J- |9  n; B1 z2 h% j, c' c3 Q
    10
    ! z: M" i. x( T) @5 ]4 u% T5 D' |11/ ]$ ~# P8 m0 i$ \! O
    121 h' d/ e+ Y9 f7 p, Z0 h6 X" d9 X1 n8 {
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:4 q# @( g6 ~# J
    . V# W0 v! K% m* }* i
    s.resample('7min').mean().head()6 F! R  }6 p- j( X. _* d4 u
    Out[123]: ) G, Y% `3 E- S6 ]' @
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值: V  @- y! e' w. W9 l9 G
    2020-01-01 08:31:00   -2.6000001 L  H# X9 L' Z) B7 c
    2020-01-01 08:38:00   -2.166667
      }1 ^1 a: r: I% N- T! x2020-01-01 08:45:00    0.200000
    % H3 A! _) c, g1 N2 S& T$ W2020-01-01 08:52:00    2.833333- z* t7 l; Y/ K) @% {- u4 q
    Freq: 7T, dtype: float648 ?5 I2 e/ q7 Z* M( \
    12 ]# U! k) d4 T. T/ g. D
    2
    4 |9 R6 f) o% E' Z$ K. I0 k3
    8 L5 j2 ~+ z# T4
      `  r# A7 Q( Z  g" H. }" }5
    * @5 l6 X6 M& @/ G65 m3 e3 |5 S  k5 D4 {& Z" [
    70 C/ J9 n, S+ I5 N* k, s3 s
    8
    7 t8 L6 p$ G5 }5 X  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:# H+ S- o% S& i5 G, G) j2 X

    6 A4 m0 L& c; h+ j- ns.resample('7min', origin='start').mean().head()
    % ~! O' m" f& D! h# _Out[124]:
    3 C9 V4 [; |1 r8 _6 r2020-01-01 08:26:35   -2.333333
    ; d0 H) N& X* y  I: y/ V: Y( x2020-01-01 08:33:35   -2.400000
      Y6 I: x5 p+ W3 Z; n0 v; F2020-01-01 08:40:35   -1.3333330 \' m7 r2 R5 O9 N+ k- U9 B2 X& e! |( V
    2020-01-01 08:47:35    1.200000
    6 c6 i( [8 t6 c2020-01-01 08:54:35    3.166667
    ' ]- K4 r, G0 o4 a0 i( x* [' N1 T' TFreq: 7T, dtype: float649 `8 Z/ m$ N) U8 D1 d9 A
    1; u. @! P) `3 J* k" i2 N8 S
    2& n* z- o2 Q; G. i+ u$ l: f# d* _) Z
    3
    2 l6 x5 W8 z2 [8 E) k4& k* w) d7 ^: s& Q
    5
    0 [' k9 @. v4 {. o) U' D6
    + [- _3 _$ s6 ~$ k8 d7
    ) \+ X: v+ _$ B6 {% X" ^2 s8
    ! \7 s1 ?* p  w/ W, k  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    : `5 H0 }9 d( A1 P/ A! E$ L  d* h0 G( Z0 ~( v- Q* r% M
    s = pd.Series(np.random.randint(2,size=366),% k7 _* ^; l6 u/ P+ _" \
                  index=pd.date_range('2020-01-01',
    - n! ^3 K) @0 d* w; j                                  '2020-12-31'))
    0 V5 Y; h, v1 W0 |% A) ?( v$ Z6 z( x  F) \* `1 m! |

    ' J1 p5 Q* P0 Ps.resample('M').mean().head()/ b/ v3 v6 @( s7 K
    Out[126]:
    ( V( K2 w5 O5 j3 a2020-01-31    0.4516133 R9 u( A: R. u
    2020-02-29    0.448276! l' ~; P+ }' {4 l8 [( [% y) z) x
    2020-03-31    0.5161297 g/ G$ i- e$ Z& A
    2020-04-30    0.566667
    & H/ N* O" g, C; ?& X& w# ^% |& C2020-05-31    0.4516131 q0 f! {$ T/ n( e# b, f, U
    Freq: M, dtype: float64
    1 v: @" y: G# d9 O; m8 d6 u; R  L* X" f% A9 w9 J1 t' ~
    s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样2 `: r2 R2 Y. J  O  z8 {
    Out[127]:
    ' U# X: j0 }. b& ^9 z2020-01-01    0.451613
    " T* @7 K5 B+ p3 }3 U% v! _2020-02-01    0.448276
    , O* {: ]( e1 L  a- X2020-03-01    0.5161296 t# D0 r" |: P" h4 }
    2020-04-01    0.566667
    9 r/ R! v0 r7 H: D% X' T! J/ k2020-05-01    0.451613
    ! ?& R: P1 x! Y/ [6 L3 {Freq: MS, dtype: float64% o  \% b0 I1 a$ M$ w$ g
    5 J- z; p/ D4 d
    1
    ( q" _4 F/ f2 H2) F7 j5 ]+ h* t/ t. \
    3
    5 ~3 G% e% a, f3 }0 {& {4
    . V. a+ ], V* c1 x5( }( R2 s- V/ G9 G" ^, u
    62 \) a9 i& U! u9 J# L: U
    7
    3 R4 w* T' Q. I3 ]; T8. y1 Z$ h, s/ X% K7 T
    95 h8 X: r* m' ^- l4 D+ H
    10
    : ~1 P3 o3 g- t11. m- G. P3 c3 D9 G# g$ n
    12
    - Q( d5 \$ G) z( u# [4 A" m- Z4 f5 a13
    * _9 \3 Y; h: p+ W. }14
    & h  i6 |. }  R0 a0 u& i15
    8 F/ u: g( z0 u0 D& u  h2 N16' t. [# \0 V" Q, o8 g% s2 r1 i% ]: l
    17! x" d' I" V  C* C( i/ @! p
    18
      X6 }- B2 @; J7 I19
    0 ]6 M7 z; `6 t3 T% A20
    + I2 f9 U/ {* H8 L& X0 b& t21
    ( g" G( D: c8 e& {0 }. [6 }226 b, L! d- F& i& @! g- c  l
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    ) T( d" q4 O' x! \d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    + e" `+ j3 V  _: w  v# Y     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}. x1 T. {$ P! I% k3 i7 |* D7 {* P* ^
    df = pd.DataFrame(d)% u$ c$ M9 i; ^: ^$ u6 y2 j# s$ D  x
    df['week_starting'] = pd.date_range('01/01/2018',: N+ i' v  h/ n& H; r( r; j
                                        periods=8,
    ( C+ N* {5 r  U: ]* p2 E* v                                    freq='W')
    : P; m2 C( w, Mdf/ k+ H" p$ z4 e, R/ U
       price  volume week_starting
    4 H. l* F" A  D2 @2 _! _% B0     10      50    2018-01-07  c+ X& Q+ e1 T7 R$ w& s" B& h
    1     11      60    2018-01-143 b/ Z* M0 H. V2 ?
    2      9      40    2018-01-21
    . @& }4 H/ n' G( M. t3     13     100    2018-01-28
      `( j  E+ X  d$ t8 W2 P. s4     14      50    2018-02-04
    ' s4 ~# z+ u1 O0 K* I5     18     100    2018-02-11- x! f) b2 K5 d( }
    6     17      40    2018-02-18. f0 P; q) x0 m1 X' R0 o& o
    7     19      50    2018-02-25
    ( a' j' _( ]! E$ ]# T' Qdf.resample('M', on='week_starting').mean()! ^5 z, k! f9 V) k9 C
                   price  volume% m8 e1 K* b# l$ a/ }
    week_starting
    6 f, B/ `4 d5 Y5 h' w) e/ R- M$ ]2018-01-31     10.75    62.5
    $ D) V9 h: m& Q- _0 p9 y, y2018-02-28     17.00    60.0
    . H5 i, |* c; t
    ! E% [( `* g3 k+ \1
    * ^% x7 g. A  P' c1 Q/ i2
      V) Z% Z( }; k# T3 |2 P# ^7 M- \' s! x3
    ' G( H" ~  X8 r" E4
    ) e+ P* J% n) a# Q' w  ^2 K5/ s5 P/ m$ v& A. a; Z9 t
    6
    # w0 F% Z3 K; l+ w, r7
    / @, z. U5 h- V. V; p8
    & F; c, G8 Y0 }9" R, l: B& H# D- h
    104 O. f3 `# W4 }9 d7 x5 k6 v
    11
    / P' Q6 V0 {0 t% t) A, w! x12
    6 G0 Z3 z. r; m. }6 M13
    . B  N: M. o, C; ^# @2 o14
    $ s& O$ r3 e7 S! n8 [7 }: M15
    8 y+ t/ U0 l( Z% m9 K' y16
    & |  F7 u2 S' c0 r7 ]. F5 a+ }17
    , N/ S& v2 t6 u, d18
    1 ^" j4 z8 s/ W' Y, B9 i19
    3 |) A# M- z% L. z- {0 b* v201 v# t$ d" [6 L% M1 X7 ^
    21  m. e5 n' n/ ?. D# M5 {2 K
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
    6 V* }1 x% A% ^, ]days = pd.date_range('1/1/2000', periods=4, freq='D')
    2 I& j1 |+ Z3 x+ v. Z7 j" Qd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],# o; f( |. b# P- E% H; [5 T
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}) L. w6 j0 @3 j; H) D7 K9 J
    df2 = pd.DataFrame(
      S  `4 g0 l( ]1 y  {( @% |    d2,
    * i9 T" K' }9 _' A    index=pd.MultiIndex.from_product(/ F% @6 j2 U" d$ Y! N
            [days, ['morning', 'afternoon']]4 L1 }& J# T/ }8 I& O
        )8 z2 B! A9 N  ?9 T& b
    )
    1 T0 K* R: ~1 E/ ~df22 ]  ^* ^' Y- q# g4 A  U2 g' e
                          price  volume
    ) }0 k$ K; C/ _$ \9 O( o) o' R' {; l2000-01-01 morning       10      50( L& K; v4 L6 Q: C3 ~% ^: A
               afternoon     11      60
    . `+ L6 h3 W% m# C4 X# ]9 S2000-01-02 morning        9      40
    : L2 W$ z7 B# a           afternoon     13     100: A- e# f4 F, O- H* o1 _; m2 `
    2000-01-03 morning       14      50
    6 u; x. q. i5 |5 j           afternoon     18     100" Y, J* a/ G7 Q! M8 y
    2000-01-04 morning       17      40
    ( @" d1 s; G1 B" r           afternoon     19      506 q# X& \; H) V7 o
    df2.resample('D', level=0).sum()
    ; @: R. u, f" m6 x            price  volume
    # D0 ?! @: z3 W# P- E  V2000-01-01     21     110$ S; F- Z8 L. U
    2000-01-02     22     1404 F+ P- q: E2 R' z* I3 t6 r
    2000-01-03     32     150
    $ M# p2 w: Y. A: @: p2000-01-04     36      90' w( w5 E" \% f1 B4 S4 |4 [- W

    ' O. N" @' t' @6 k1 n1& u0 L$ C5 i0 @9 W- ?  ?
    2
    / p( G; i4 w6 [3+ N2 V+ H. p: S1 {4 Y
    4- ~4 C2 X+ \$ S' {4 G) ~
    5/ E1 [- H4 z  @/ [0 a! Z3 W  R0 u
    6
    + P, `: B. }- z0 B5 w9 R7
    $ `& x5 I' F3 J4 F* h  k1 }8- {* W2 K5 {$ W0 s2 V$ t5 X: @+ V; ]
    9
    9 [# C4 B; a( w' a# W8 _' d10
    1 O& t9 n# X& Q3 }11: K9 V1 }# {9 W9 \( m& K, q
    127 H2 s8 Z; X8 N! R& P
    13
    2 V7 H1 |6 `7 C1 T# f+ c14% s/ z, I' A+ |. S5 P& c6 ]
    15  j9 a$ g+ z3 X) H
    162 Y6 P: ]& f% f5 ^% L  W1 E* C
    17+ t  ^. x( h( D+ ~! O9 J6 ?+ q8 y
    18
    8 E4 E) j2 Y: q! e+ I* X3 k) e19
    & W$ _4 N% \' w2 R, U20) U& L/ i" Y6 {2 y  L! I
    21
    ( {" W: R, _' ]  d& A7 P2 C& i222 S5 B) a3 M+ V9 i! L0 K
    23" H6 n: U2 r: c+ Z; p
    246 ^* S! ?% @8 a( I6 f; V! L
    255 S3 s+ F' B7 s! e0 ^
    根据固定时间戳调整 bin 的开始:
    5 l3 D. ^. [& _0 L! C5 c1 gstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    % d* l: J+ g' U9 krng = pd.date_range(start, end, freq='7min')2 q* I1 D+ m$ a1 p, _
    ts = pd.Series(np.arange(len(rng)) * 3, index=rng)$ F! \7 d$ T2 q) A& Z- ~: M2 V6 P
    ts, N" O# r1 T" \, j% O- q
    2000-10-01 23:30:00     0
      M( X9 w, X" f* x+ C) I. O2000-10-01 23:37:00     3: F( N) O% n, ?  c. V* b- ]7 Z
    2000-10-01 23:44:00     6( n$ X# D. x: |& X9 F  h
    2000-10-01 23:51:00     95 m' |3 V6 a; H! I* S
    2000-10-01 23:58:00    12) l; O  i2 ?* S- B! G. n* R" V
    2000-10-02 00:05:00    155 }* o) d+ Q& K/ A0 [
    2000-10-02 00:12:00    189 V* S/ ?4 `! x  P* X9 {7 Z
    2000-10-02 00:19:00    216 B9 w& t$ }4 x, c6 ^  u& g8 M
    2000-10-02 00:26:00    24
    5 n9 B, _" ]* |1 aFreq: 7T, dtype: int64) m2 F* G1 W0 ?& L9 h% g
    " U; D( G- t; R: y
    ts.resample('17min').sum()
    0 _6 ]; `  Z, F  n8 O" M2 Y2000-10-01 23:14:00     0" o9 o/ [5 e+ X# q5 ]7 G' ~" G+ ^
    2000-10-01 23:31:00     9  z+ _6 y! `  e9 [0 F! D3 R: s4 G7 R" g
    2000-10-01 23:48:00    21
    * Q) ?; I3 z" V' P2000-10-02 00:05:00    54
    # l+ p) `! q9 N& }8 G$ N2000-10-02 00:22:00    24
    % f% `8 G9 G6 z- n! q! }2 IFreq: 17T, dtype: int642 L5 [( `4 `  c! u

    , p9 v# L! C. h7 [) S, |- Q1 Lts.resample('17min', origin='epoch').sum()+ a# l, l) R- q# _* u; @9 s& x; d
    2000-10-01 23:18:00     0" ]- d/ |3 y6 s3 @
    2000-10-01 23:35:00    18
    9 f: ?/ r8 _; Y0 H2000-10-01 23:52:00    27
      L, Y, |7 p+ e2000-10-02 00:09:00    39
    6 x3 w. v: `. E$ j; R& j$ l2000-10-02 00:26:00    24
    ; v# E; k2 z/ z1 jFreq: 17T, dtype: int64
    : T0 R3 |2 V: p: I3 g% c9 ~7 k- x9 n7 S% h* x
    ts.resample('17min', origin='2000-01-01').sum()/ z7 t$ b9 B' C3 c; @7 `# s0 A! q) L/ r' {
    2000-10-01 23:24:00     3+ E; j: A: R5 x  o
    2000-10-01 23:41:00    155 K! }6 B, z2 r4 s. v6 V
    2000-10-01 23:58:00    45) i4 y& U& n% w1 X
    2000-10-02 00:15:00    45
    " x. ]6 [7 Q: g5 e- VFreq: 17T, dtype: int64
    2 a$ ~, f) r1 K+ |% R, q/ h. x. Z+ J. C1 B2 |. n8 q
    19 C/ \2 q+ a+ [4 F( h; M
    2/ P. m0 M1 ~3 V1 ?
    3; h( t. O. m5 |2 u
    40 r! t9 K0 X9 F
    5
    9 v( G6 d' r& H8 `; W; F6
    3 C& F  L9 O4 ^+ E7
    8 L) t% Q! u1 W81 l' r- n/ y4 e6 d6 T
    9
    & ^; e5 L3 G/ K109 ?+ j1 p9 }) Y% V6 \
    11
    3 y( B( N2 z8 s' L; M12) ?2 O( ~3 Z0 Q" `% U
    135 s: N4 \, g9 e5 i* z* c
    14; X' Z3 k! v$ z7 p
    15+ t$ u! g+ T: j5 s4 @6 q
    161 r$ l) L: Q4 U
    17
    * }4 C' ^* t( o( p. y18
    2 s  \0 [$ E. Z9 J$ _5 x7 [) g9 z6 M19
    9 J7 T4 q, ~# t20. q. N  P% [( Y- O/ i' k
    21
    3 ~) e9 n# |& d2 z5 s* ?22
    8 q' h0 ^! h2 |, @% N& B4 m9 h23
    % w; K, Z: T" }0 w0 R24+ d" A$ D: ^8 }5 I0 u4 M
    25
    , H0 b4 ^2 v% k% S2 n+ p. a26) F# q" r! ?  [- {4 p9 C8 L) p9 s
    27" B# U6 c; a* N/ k7 }4 H6 w8 X
    28' h* i4 I8 Q6 M$ u
    29  t$ t3 A5 [8 G5 p
    30, V# K# @! i! U3 k& \
    31
    2 Z' f7 r7 D& y5 f" m1 B. H, g9 _! k32
    2 J+ Y; q4 c5 t+ n2 C9 Y33" F; r# }. g' J) Y
    340 _# l4 O9 S1 X% d
    352 e$ f9 K& n. E: v( R
    36
    / \. l9 F/ J" B/ g37* j1 \/ J& Q$ B' B0 J8 {# \
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    ' b9 b: l6 K* B' zts.resample('17min', origin='start').sum()
    , o+ b- F  l. s8 c+ X3 Ats.resample('17min', offset='23h30min').sum()
    . j* F8 g8 R& l$ ]% |2000-10-01 23:30:00     9
    , g2 {& y: X0 D; c; m7 E& m; Q0 {2000-10-01 23:47:00    211 l' }1 w! I( N! H6 f) o0 a( Z
    2000-10-02 00:04:00    54
    5 X3 e- z: Q& U# L2000-10-02 00:21:00    24% {  A# F- T9 l0 Z" n8 ]
    Freq: 17T, dtype: int64
    % E: R" L3 ^: |- h; t# q7 l10 E) _" T1 c1 h/ e0 T! T
    28 g8 R8 n* a! c* ~
    3% s8 z# w- K( S; W1 N5 N
    4
    7 r/ n$ o) }# C5
    % g7 _1 F( g& _* P4 l6( v0 I. D+ ]* L
    73 t  L$ |. o8 u
    10.6 练习! \1 i* L# Q" Y2 ?3 B8 w& L! n
    Ex1:太阳辐射数据集
    4 v* s7 y# g  \  o/ I# _5 T现有一份关于太阳辐射的数据集:& F* X4 _2 L1 e6 |
    ! r  c4 I+ ]& Y. S: s5 v; N
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])0 d# v/ ]& l; ]# x+ A+ @, r2 c4 K
    df.head(3)
    ( g% G: y$ s, O0 l7 k, `5 t
    4 I/ u1 G# E8 d! tOut[129]: # ?' ~' B8 a9 \. e! w0 y' M" i9 \
                        Data      Time  Radiation  Temperature2 A/ H' ?/ ^8 _8 i3 v
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           48+ W  o3 v! L! j* D  l% A0 Z1 G& y8 g
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48. B0 ]+ p. n7 A& v) Z
    2  9/29/2016 12:00:00 AM  23:45:26       1.23           485 r7 K3 q& ^! V7 w* X+ m3 V; n
    16 M9 X. E% ?+ k  x
    2$ h2 g) H9 C. F' x: f0 P* p0 K
    3
    ' w4 c0 x* w( x3 l; v- c4 [+ Z4
    8 Y# t6 a" j+ _% F5 V4 c5: x5 s  K2 D6 Z  U9 E' k; y8 E
    6/ i1 _8 h( E1 A3 A" f( S0 ?3 M
    7" N2 t! {# B( G2 `9 ], g
    8
    ! T* x; L: E6 ]; [将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。% ~4 L2 G8 e' r0 e8 W1 H
    每条记录时间的间隔显然并不一致,请解决如下问题:( Q. ^3 d2 {1 `0 X& F/ @
    找出间隔时间的前三个最大值所对应的三组时间戳。8 i9 b+ q; o) ~7 W3 r  b1 J
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    6 ]0 z3 a3 V5 }: C求如下指标对应的Series:
    ; C- _7 w" v0 F! C1 g! C" l温度与辐射量的6小时滑动相关系数: ^' v' a, G8 M
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    ' c% l  Q" p+ G7 ^  J& n$ P( p! v每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    5 B: w7 L# f% limport numpy as np
    6 H* m& q& n' z; k+ _# yimport pandas as pd
    / [2 q- |( j9 b9 X* K# o$ ^1( @! Y! ]% z: q# E
    2
    / L& Y+ j  U1 A将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。( y/ i" y- o9 `0 _; d" |/ E
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
    7 H- O: t; G# L/ ?  e# A) V4 x" ntimes=pd.to_timedelta(df.Time)6 B9 G! s( G9 \
    df.Data=data+times
    $ f; }0 j8 ~! _* v* d; G" l( ?del df['Time']
    ) w4 Y6 ~+ E0 S! q% _df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留  h% W- {$ O- i9 v
    df
    6 k- C0 J2 D- O+ }                                        Radiation        Temperature
    ( b) i1 |: `3 GData                - J; }* d( t/ S9 K% R! W
    2016-09-01 00:00:08                2.58                515 d% @8 O2 ?8 A# W& M7 v
    2016-09-01 00:05:10                2.83                51( E3 P" K: p9 M" C1 T3 @" M
    2016-09-01 00:20:06                2.16                51: H, M" q5 m2 e$ m  g4 n
    2016-09-01 00:25:05                2.21                51
      @8 [' `" f! d6 w: j2 @7 L2016-09-01 00:30:09                2.25                51, a0 r4 j& s$ G* J1 G" @
    ...        ...        ...
    ! S7 P$ l7 r4 ?, I4 \8 C  P2016-12-31 23:35:02                1.22                41
    3 C; W2 m: L3 w, O- j& X2016-12-31 23:40:01                1.21                41
    $ S8 S4 b+ d6 z3 R/ O2016-12-31 23:45:04                1.21                42
    . N0 l- i0 K4 |# _6 X. V2016-12-31 23:50:03                1.19                41
    7 `$ i# I/ j, i" ?. m! T3 g0 Z2016-12-31 23:55:01                1.21                41
    0 [' ]% K1 M( ]5 B$ t( y; U& [5 z# J
    1) W( o9 Y8 u" Z- M# |; u9 [3 r
    2
    & \6 Q4 ]0 l6 z* X3 s! w" I3
    # O: P) |8 m2 S0 C; G# N. V% B4
      A. _: t) u. B! Y1 W( o+ G5! p  F7 W- |, L$ g% U
    6. y5 G) p/ ?% t( u7 L1 Z
    7
    / c2 r! P% x7 ^8$ r; w5 l" e6 b0 b8 [% Z
    9
    9 S6 F7 \/ O- c1 e) w10; q) c6 A6 `- `+ w% A
    11
      x3 r9 d3 d6 T- \# A12
    9 T$ |+ {* x: G/ g133 T" A7 i( m. M. ~
    14
    6 [5 Q5 J1 R: L- |! p% |9 D15
    5 c! }: |' l# }5 x168 s+ k# D, B# U; r
    17, X$ P2 T+ e; v2 G: n
    18' A8 |, g7 v* S" Z3 _$ U9 _
    19+ M6 L* ^6 N* j2 s/ r& O3 v
    每条记录时间的间隔显然并不一致,请解决如下问题:
    ( |( x) U9 t; }0 j$ \3 i找出间隔时间的前三个最大值所对应的三组时间戳。
    5 p5 a- c4 X1 a# 第一次做错了,不是找三组时间戳
    5 h- R7 `0 N$ e: }/ o. kidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    : U+ v3 h! \- [df.reset_index().Data[idxmax3,idxmax3-1]) S  u5 B9 X( T8 e6 ~( u1 a' z

    * ~2 [: ?7 h8 B) b+ d25923   2016-12-08 11:10:42
    # u+ M) Y- d) V9 m0 T24522   2016-12-01 00:00:026 O! e& }  B: z! K$ o: t
    7417    2016-10-01 00:00:19
    ( H& a- i, T- Z  W( }Name: Data, dtype: datetime64[ns]. ^/ \5 v# _" c! V: m# w
    1  G5 d( `1 m0 h3 ^3 H$ p! t
    2- [. P% ~# A. d1 ]& l+ Z* G2 e
    32 P( L2 v3 _1 ?/ }) u
    4
    ) u9 Q& c; H. ]4 c5 F51 P" M; f8 ]/ J
    6
    ' R; f5 \) C9 X; l7 l4 m: R/ s$ R7; {9 L4 D4 v( P& |4 Z5 b  o  T0 K
    8
    / t5 O8 B; l  t, a: Nidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    . [9 e0 Q" n- |& h% i3 L& Flist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))( P4 C3 n9 A3 ?3 ~: X

    * ?& p1 b6 Z' z' K8 m( T4 c[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),) X# |. @6 k% F- W
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
    " u  W9 P0 @* H- E1 c  G! B% ` (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]2 D& }  A( P; P) F
    1
    . p3 J" {) ], g$ g5 t; a# X. Z/ j2+ m# I$ Y0 i8 _) a& ^1 F* B
    3
    / Y: B/ i' K3 t# |& M2 s+ l3 z7 V: a4
    ' P: I/ X. }+ H2 O1 {7 K8 l0 X5
    . Z0 m/ e: Q; u4 r1 t3 _  e( k+ H6( O( M2 z: b. I- V
    参考答案:( }- d9 R. w$ ^5 A+ [# l* [
    . @4 S5 j7 R& a9 p) y
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()1 Q9 x& A! X( N+ Z3 M& E1 E+ ?
    max_3 = s.nlargest(3).index
    # q: G3 `" R/ f/ S. \df.index[max_3.union(max_3-1)]
    4 R- O% H' ~  R7 `/ F0 K: {: b' c5 y) ~- d3 E9 H( h4 L. q( z- T% g
    Out[215]: # ]/ U  L5 l  r/ z  [
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',% b% B; X$ G( Q3 g+ h
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',
    9 U' A2 x+ s9 P               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
    ! f" i, R9 [0 D              dtype='datetime64[ns]', name='Datetime', freq=None)
    ' Z% y2 y, B, z) f7 w1 Y1
    ' D+ o4 s3 z0 i/ @7 R- L, F& P2
    ! N. ?4 `2 K/ g* w7 }3
    . Q* k; C1 p& ^& p3 r; r7 Y4, K" F  w* `9 C( b* @
    5
      ~, v) _" V! `: q- R: Z- L! h# v: s. X6  ?  J% P' V1 D) p: m5 z" }
    79 F- m) E) k: h4 w" w( F
    8! a( x! u7 L: \9 }; e# z7 V
    9! y$ K1 Y) F* b) a; ]9 l' L* M: p' E
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    ' J" |3 k, `9 N: d) M4 v; J# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间' g# E2 }$ J7 }9 Q
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    2 H* Y' W  a# m1 I/ z8 Y: zs.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
    5 F6 d9 I+ V6 o4 T4 f! n/ n+ H
    , t; G% B* q4 v) `3 @& H(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    ; W: V$ e8 G! i3 ~18 ]; k) y+ V" }* G# y" \3 }
    2  O/ b( ?; O- p  J* P5 d
    3) G4 C0 P  G) @) }# \( I& o" m
    4
    # f* C/ L- q8 ?- h5
    7 E7 g: k  L7 ^/ S; ?+ ^%pylab inline1 u8 ^4 l9 ^7 |3 h
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    # {) `  E8 J6 p/ [" t/ f! Aplt.xlabel(' Timedelta')
    9 Y9 C( A4 z5 O0 ?plt.title(" Timedelta of solar")# ~3 q; y7 e' L+ @
    1( ], W3 a* Z# V0 [( j5 U
    2
    2 C7 h' Q$ I+ P: a0 {" R4 x. t+ e3
    8 ~0 ^/ Z  g" @. h% }4 d% [: Y9 p4' J0 j; |  Z  ?' C! \; t

    ) u9 X% _% Q& i6 ], r
    6 [/ S9 p4 [5 K6 d求如下指标对应的Series:  o; {) Q  x" N: W3 a& k8 e) B
    温度与辐射量的6小时滑动相关系数2 F0 V! @- m! X
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    2 h' e3 t: {* L) m/ T9 ^每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)9 M9 f! \3 D: D7 R! E) t$ S
    df.Radiation.rolling('6H').corr(df.Temperature).tail()
    / O- W  m3 U5 G: D2 S5 X+ i/ q
    4 Z; _4 b6 b* EData
    ( C1 j3 e& U2 ~% H, E. k2016-12-31 23:35:02    0.416187
    4 E" \% d+ m, i4 b  q  P+ ~2016-12-31 23:40:01    0.416565
    $ |  ~4 o4 r+ P2016-12-31 23:45:04    0.328574  I  R8 C. U3 g
    2016-12-31 23:50:03    0.261883
    0 q6 G% }- S0 ?% e. L2016-12-31 23:55:01    0.262406
    + O7 F4 t: |5 J0 X- ~  e4 r) I* @dtype: float64
    1 o3 w6 P- Q  U9 F1$ K* O5 K4 k" O" z1 ]
    2
    + ~  w  n+ ]' W1 F. E3
    - `, m' d6 O9 z! y6 f- ]4
    4 I+ ?" [9 [4 [, n" {4 i55 Q+ ~- o& i0 a5 U* Y
    6
    : `2 E! a0 A% m! j* v7
    $ f; H5 l$ ^: u2 v0 Z8
    9 ~4 M# p* I$ r) d5 A97 D( W+ F& ~2 L8 k
    df['Temperature'].resample('6H',offset='3H').mean().head()$ w5 l0 e& t2 v" [' p

    ' W) H! s9 X  A1 q8 _Data/ R% R6 p8 V! t: N9 w" j5 v- j: z
    2016-08-31 21:00:00    51.218750" q/ a6 j. P- R& W$ O1 M0 l7 h
    2016-09-01 03:00:00    50.033333
    4 y' ~& m7 ?* u% l1 M) ]2016-09-01 09:00:00    59.379310
    & L' _, @) T2 e* E2016-09-01 15:00:00    57.9843759 h2 Z" C0 N/ O3 C+ a
    2016-09-01 21:00:00    51.393939" _3 |9 I9 y% _
    Freq: 6H, Name: Temperature, dtype: float640 Y( v  g7 y! z* q$ W) @0 y
    1* [# D2 V6 y8 C- v8 ~% L
    2
    % t( S; f, w: u, Q3
    : }9 k) Y  u) k* ]- u' x4
    $ N" k/ O1 l# t7 S' h( \  J55 }  {6 ^& a0 J% o, Z! @" ~- Z
    6
    ) Y% C9 p. E# R2 ?4 l) f- f# z7' k: J1 S; x# ?) N) Y9 l
    8
    7 L4 z+ r5 ?2 G1 T8 J92 z' H+ N! X* W( N- }  [1 i6 W
    最后一题参考答案:
    8 M, C8 Z' @9 O9 Y+ Y7 y" e% p/ b3 t! B4 L
    # 非常慢& G& y" y! u6 @0 H! z& }- \. \* Z
    my_dt = df.index.shift(freq='-6H')
    2 b* a( v; T9 ~/ |$ {" {3 j; gint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
    7 E2 Y3 c0 i; M6 }; oint_loc = np.array(int_loc).reshape(-1)7 P2 @  Z( ~8 B: x! q
    res = df.Radiation.iloc[int_loc]
    * E& `% ?1 j+ A4 }! h7 hres.index = df.index
    % f/ u0 A) D% Z: x8 ^res.tail(3)
    & G, ~- L5 _3 c0 g! c1, L1 m( Q# l# r- K; q
    2' s# [% _" X( U6 S
    3
    ) ^% h. o3 T8 ^6 _& r. i40 n5 a) I9 s+ i; @
    5
    6 p; [$ ^4 \/ Q7 q9 S6
    ; ^! Z7 A4 T# H, V7 h6 t# D7
    ( O$ P1 C% o: x8 j0 T# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    & O9 Q; c0 v' @/ wtarget = pd.DataFrame(
    9 f" o, s# P- s8 P    {! N' j. L# |- Q
            "Time": df.index.shift(freq='-6H'),2 Y9 p5 }( V7 `; O
            "Datetime": df.index,
    - U& d3 {6 _# B( f" K6 f9 t    }
    ! V/ ?6 M5 C5 S; v* b; N; Z* e), Q% l3 |3 |: p# N8 T" A1 j
    . K2 w  x/ F" u% O6 ^' Y7 I
    res = pd.merge_asof(
    9 f* Z4 E, E6 T7 Y! I$ T; G8 f    target,
    ! D) v; s$ y7 J1 R! z3 x    df.reset_index().rename(columns={"Datetime": "Time"}),, c3 m# ^; a' \+ |# j7 f' J3 |0 K. r
        left_on="Time",
    2 [. K$ }; P# `) m1 H# T    right_on="Time",/ Q! }$ W* {# P9 [8 m# @$ W
        direction="nearest"1 a1 O* N  e& M7 i4 y1 s
    ).set_index("Datetime").Radiation/ e  S* x. [! m. ]$ I

    6 l! w. ~6 m  b% u( J& ?$ mres.tail(3)8 e# N/ B4 [( N0 d
    Out[224]:
    & u5 @# I" F  a3 [" j! MDatetime1 V# N! I) `0 M0 c; W& S0 p
    2016-12-31 23:45:04    9.33* g/ L" R$ N# }
    2016-12-31 23:50:03    8.49+ U4 f/ X5 p8 Z* j0 K4 P
    2016-12-31 23:55:01    5.849 E; n# D2 s' `% ?! m) A/ \
    Name: Radiation, dtype: float64
    : X7 @* ]4 e  M! K  S3 ^8 L
    - E6 ~/ N( T5 _5 V6 ~- q1
    # f4 H1 `' F2 X& L8 T2, l  P9 r6 @* t; @
    3
    - c( P5 y+ ^8 d0 j4
    4 p6 m9 n. m1 l1 }- a  T1 X- e5
    ' f" S! d. ?, ?# i9 o3 I$ [2 c6+ u' ]3 B' U$ f- o1 c& {
    7
    # \' O, y& Q( Z; W$ k6 @1 j' j8
    / _6 W4 a" o1 v  d94 t& g( v- j' y# _
    10
    ) B0 C$ i/ H3 M" ~. F/ [9 y3 n* C11# ?* B9 S: [; E3 z- I9 `
    12
    + @/ G7 O. d( R3 V13, G& Q5 k- `0 E& }# s- a
    14
    1 u, C; \( l  f# f6 o% q* H15
    % S1 n; I: @8 b% e3 K- q, G16
    ! m/ [1 f! p0 g% E$ N2 Q1 N17
    + a  ?: P, i3 v1 @18
    , x' t# h! ?; f19
    2 A% {5 ]3 a1 r* F8 A20
    ! M8 z7 @1 v  E/ x0 ]7 v* M21
    , ]5 [* j% ]/ h- L' e0 ]22
    # o" `; K8 C7 P230 C& @8 X& W; g( W% v7 Z) N# w5 o
    Ex2:水果销量数据集
    : ]. r7 D* H# `, }+ E+ W) z' S8 @现有一份2019年每日水果销量记录表:- N) e# O, ^0 ^3 ]6 x, n, B6 I4 E

    . l& Y1 U' V9 k% Y; \df = pd.read_csv('../data/fruit.csv')
    + Z9 k2 @) ~8 k& {0 zdf.head(3)
    % V- q  S; V: Z  x1 C  Q. G. ~% K! Y( y+ I! e3 ~& W
    Out[131]: $ b9 N2 e3 A6 C9 y
             Date  Fruit  Sale1 v) T, |9 e! C
    0  2019-04-18  Peach    15' u$ d( c8 z  V
    1  2019-12-29  Peach    15
    * ^3 e0 @. s: R& I, v9 J# R, R2  2019-06-05  Peach    19
    ! Q  X) u- c" i* O3 ?% _1
    6 K1 s" c( Z) P2 {28 `+ @7 Z! E- Y
    3
    ) L- p+ [- {5 z  d5 B6 O7 z42 {9 L% Y" ]% @" n1 k
    56 G9 z# z# Y! S+ o1 b/ }7 W4 \
    6* v, r  `( v1 H' c3 ]0 o; u
    7' F7 c: C: [7 |1 p0 @0 H
    8
    ( {6 P$ i, u) C& U$ D3 `3 w统计如下指标:! z1 c; h2 w. w' d- |. {
    每月上半月(15号及之前)与下半月葡萄销量的比值! h$ W7 S$ c- M% Y/ N$ ^5 k# F
    每月最后一天的生梨销量总和
    " M& Z  O! s0 j( B& _, t' K. t每月最后一天工作日的生梨销量总和  p8 C, a! d1 W
    每月最后五天的苹果销量均值
    4 R' h" n2 |. ~6 o5 n& h按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    , Z3 o  _2 l6 h按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。; L2 @' a7 U  T  i
    import numpy as np
    3 w' n. j! t- m( q5 p! Himport pandas as pd, k9 u2 f. |& L3 m* ]
    1
    : ~' e' Y  y6 l: y2
    6 T: W8 L( _! _2 ^统计如下指标:, M- q2 o) V& _5 l& `; F
    每月上半月(15号及之前)与下半月葡萄销量的比值
    % o/ C* h. H0 M, z- e0 r) }/ g每月最后一天的生梨销量总和
    . A' l: I; P2 t6 a0 W; i5 f; l每月最后一天工作日的生梨销量总和
    : j7 ^8 s! {' K1 X2 [; V, C8 E每月最后五天的苹果销量均值2 n/ a( ^5 m7 Y$ x/ K" q2 n, T+ E
    # 每月上半月(15号及之前)与下半月葡萄销量的比值
    ( @, g/ S' v+ ~8 M, ^df.Date=pd.to_datetime(df.Date)' x" z7 ^. G: i+ f
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()  D4 g: a6 S6 f8 n# [
    sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    1 Y. f) x* m5 V. o2 |sale=pd.DataFrame(sale)& c; `+ k$ O/ X$ x& O
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    0 w: @$ R% c( Z% i7 A' e  }' P                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    9 i2 M$ o* t; ^% O, dsale.head() # 每个月上下半月的销量0 G2 w# L( q! Z5 v1 }! ?  L

    + G8 n2 G  h( Z+ L  Month        15Days        Sale
    ! c- C# e1 E* e% s* I( |  I0        1        False        10503
    7 g- d% h2 U3 M* Z9 r+ K5 t1        1        True        123418 p- r, `: r  k: D+ I! d
    2        2        False        10001% @/ g3 X5 g! H7 g- J5 x0 T
    3        2        True        101064 s8 a% I" ?# W: H2 E
    4        3        False        12814! O" K# t0 y7 F* n' c$ c  [

    * G4 D6 l7 x& v9 ]0 \, z+ o3 r) L( d$ |# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序6 n- y6 a. _- T+ e& o$ X* {
    sale.groupby(sale['Month'])['Sale'].agg(
    # P) B, ^: L! i  X- u. }                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())! b, x6 h+ |% z

    , Z( W$ M3 r1 Z" E, SMonth
    % h9 T, C; G, a# Q1 K: Y  s1     1.174998
      }. `% q% J0 j0 A* |5 p9 {1 r2     1.010499$ j8 b( b: P+ v* D  n" K
    3     0.776338- }! |; W( L( ?2 _8 h0 ^6 ^
    4     1.026345
    + ], ]3 w( X+ z0 x/ f5     0.900534
    , R3 y# G1 G7 J0 t" D6     0.980136
    ( Z: `. ?3 v/ H/ x7     1.350960
    4 Y1 {* m6 z" ^6 o! |8     1.0915841 a( k' G$ F- ?8 ^1 u& m
    9     1.116508. w9 M1 t- a9 w+ Q1 @! s
    10    1.020784& _; d' d; t' U; S- B* q$ ?8 M
    11    1.275911
    , R5 s$ K+ z# h, f* P$ @5 a12    0.989662
    + z7 R6 ~! N/ ^8 j$ `Name: Sale, dtype: float64, R4 @9 m! v' ~' W

    ( _( @5 D" i* x! T1+ P6 O4 `: x9 a/ d& x
    2! D# ]+ y) X+ E/ v9 }$ R, z( ?* H5 P4 H% _
    3
    - c, B! t, C( }0 {0 V4
    ' X# ^  T! z2 G2 G+ U  O6 W- p51 F3 V+ q# ~6 P# t" P% z
    6' h/ b; _; \. i1 N& D% b
    73 T2 f/ q# c: Q9 g7 p0 F$ P
    8
    & L+ y3 M' s- Y: X) S9 u9
    + k  r3 t6 r9 K( z& I; J/ v9 L104 L0 I+ L, \  n+ j( I
    11: Y/ N3 w4 l9 B
    12
    ( ]' V; B  G$ i* v, }- a4 b13' e, u+ C) O9 O' P5 i. g
    14
    * r4 I, f5 {8 R' J+ U8 D8 C15, Y, r/ E& [+ N& g
    16
    ( v% O0 h$ T7 k" C+ F8 `176 g8 V1 H% C" a( r
    18! L9 ?% T- |  V, O0 ~) S
    19. Q* S" F* e* M
    20
    ' T7 Z. g. K2 c. g/ S. h/ E21
    / f5 c6 w$ O& H! p* h$ c+ C22+ N4 B% r; g2 g- G
    23
      [/ Z/ ~9 z9 p8 T, _7 {9 ^$ K$ d6 m24; S8 K1 D( Q9 E" T3 K
    25
    % i# c. [$ R2 Q26
    $ R% S2 J$ }' s; |( f+ d27* H. C0 V1 a2 w* }6 P
    28
    3 i. ]! w0 g# A& N6 I. q29; q0 G# ]7 _5 Y" @0 G
    30
      ?$ a6 n, ~9 ~5 h1 j312 t! m( x# I$ F, c0 {. C4 N
    32
    & k4 Y1 a1 }! M) E- t/ o; \& R334 b! Q4 A. i! f+ A: F
    341 M! n) \. R! A' i
    # 每月最后一天的生梨销量总和
    , e4 m# ^0 T$ z9 V5 t9 e; m# w/ odf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()' z* W4 z4 m& V

    " ]7 E( j$ b2 _" t) p% fDate8 Q# e" v( g4 x6 \4 d2 y
    2019-01-31    847( F: M5 y0 }3 B$ f
    2019-02-28    774
    " t9 ]1 l, H4 x! d0 s4 M4 s2019-03-31    761
    5 r2 L1 H) W9 ^; ~2 T2019-04-30    648! a+ U) w' h+ }  q9 A7 D
    2019-05-31    616- W& k7 N  E" K2 f8 C
    1
    ) u8 y* G5 m$ H  ^2 o/ O25 S2 F* W: m8 d6 R' |+ {. ]
    32 C, O) h2 _. Z! b# _
    42 h3 X) ~1 y( C7 j$ ]( W: P
    5( A% N& B- a! [! I& I: u
    6+ w, l( j: W% Y- X
    79 D6 R( X$ b& S$ [! w% [/ c" q
    84 T2 }, t' U4 o. @* k
    9
    8 Z1 z/ e" t4 }+ {* E" i+ K2 K2 U4 \# 每月最后一天工作日的生梨销量总和
    + |1 M2 f; K! v- E9 Dls=df.Date+pd.offsets.BMonthEnd()
    5 E5 R1 X! x; @1 ]9 C- H7 q: y2 G+ Qmy_filter=pd.to_datetime(ls.unique())+ F) `' W# X1 u" X- u& k
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()! P2 v: C* @3 {, V0 e

    ; l, J5 ?/ F0 \4 a* H% YDate
    5 ~! m+ O: A# U3 n1 I2019-01-31     847
    ' x4 D" e3 ]( x; s8 \' `+ n; d2019-02-28     774  F: v2 H9 G, r4 z- v6 M; x& w$ k
    2019-03-29     510
    6 L8 C: _5 o& w0 s: Z, @, T/ r2019-04-30     648
      D9 W, K5 D7 k* n0 q2019-05-31     616
    * p1 [1 E: M9 R, O1
    1 {# K7 [0 I- a2
    8 p+ R+ b. O( `" X. b3
    " `9 `! y9 i+ F1 h6 f) z' j8 [. D4  m+ h0 X0 R. o" K0 _
    5/ P0 b+ o+ H! d8 ^
    6$ B8 D* |1 \5 V& D+ f) l6 r
    7
    4 ?& |* h( c/ t9 _3 v0 V2 [8. ~* w8 {  ~! N2 q! @% `
    9, z" P: R( b. s
    10
    % F7 e3 ]! e4 ^5 e3 F+ z# J8 u; g118 |) @7 M6 ^* ^  d% f1 j; {
    # 每月最后五天的苹果销量均值
    : Q: T8 o  c( P5 E* s, U  u/ astart, end = '2019-01-01', '2019-12-31'9 x2 l# d, p5 s  M( s/ X. M
    end = pd.date_range(start, end, freq='M')2 G" }; Z4 s0 G3 }2 F
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    ' x6 v. g5 E  ]- X7 c6 V; U  S& Y- r7 U* ^( @) J
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    , U: O7 h0 P& j' ntd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天6 y( L! t" O, B6 R% o. [
    end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表7 e3 Y4 z$ P' l; T6 X+ `- ]
    3 M7 l: C3 P( G) j& {  f- O
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    2 [( T3 \  W& ^' f3 oapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
    ; Z9 [, J5 z7 E! B; b- T$ |2 P0 c! z% e7 Q) F5 X
    Date9 r7 T$ x+ S; J& Z. {3 X* h
    1     65.3137256 O; z! G3 y0 G, I$ E( e9 |
    2     54.061538
    ) t; N. z4 ^5 W. z3     59.325581
    ' ?/ ?( E3 C$ p/ T8 j" [  B4     65.795455
    7 g' i  g/ @- D- j5     57.465116
    ( e2 O( f1 P$ {$ p
    $ i6 c! d6 m% a5 |( I9 a) h$ N. H1
    1 D! o, V* d. Q- v  U3 v2
    4 C; `) r- R" ~# O. o3
    , w- ~8 W" D+ a& ]! V$ U4 Y8 u4
    ! w' g5 O% G: e58 V6 W3 a/ b9 \
    6
      a7 }, x% J( T7
    * K. w7 b3 O2 h* e$ `+ e5 Y88 L/ q! F, y$ M% {: e7 J
    9; m* @* `% w; h9 O  k
    10- n  B: R" b8 v' N' A2 E
    11& C, f6 e$ j4 ~: R  [, A. q# w9 P' I
    12
    2 ~8 [: }  N" |. r# R) }, S13
    # e9 V- I& z6 k) B$ t  z14% b" V3 c# L, i
    15; m3 w3 x+ `9 ~; t
    165 M' m) K$ G: X" I# I& u4 H
    17
    3 Y: V3 }. X' p) p18
    / @; g3 o% k5 ?6 C7 B' l# 参考答案:  K  R9 ^" [5 ~- d7 V8 U" ]
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    5 K0 Y8 Z4 R- \; n            ).dt.month)['Date'].nlargest(5).reset_index(drop=True); x9 T* Y' D" ?  G) c

    ( ]5 M) o& S+ c" [- w5 T" C: U/ Pres = df.set_index('Date').loc[target_dt].reset_index(
      m/ k% j* \6 B  z" N5 f            ).query("Fruit == 'Apple'")( j! t  l  c4 R2 h2 Z8 u1 l
    2 J: s: b+ m6 ~! r, [4 J$ Z
    res = res.groupby(res.Date.dt.month)['Sale'].mean(# I* H* K) T" k0 [8 d7 t
                ).rename_axis('Month')( ~7 u3 k8 I: c& p4 j9 j
    4 A0 t0 _1 `; ?7 O# C

    . C$ ~$ O! E; k: ~) K. O* E4 t! cres.head()  }9 h9 J# O* p% W; A/ T' U
    Out[236]:
    3 m" U0 V5 u+ K- tMonth6 ?/ G) h+ V7 Y* L% H3 j
    1    65.313725
    4 V* t, T# X+ u. E. Z) e% X/ O0 P2    54.061538, m' F7 E8 r9 w4 k( C' k9 C4 q3 _
    3    59.325581
    ) k9 G9 M, F. J4 E! m' y- d4    65.795455) a3 ~2 C5 |- {2 F* K
    5    57.465116
    " _" [. w: @' y/ nName: Sale, dtype: float64
    6 O6 P9 j1 \' H- ~. N' B; @9 {% N9 m: |. j/ f, o
    17 M$ b4 W+ J% z! W4 J
    2
    8 J: S( J& D$ ^0 W. @+ |3$ p) \3 V8 ]2 G3 E, I, o, N
    4
    ( \1 |6 r4 r* u# X5
    0 i/ K6 j* O1 L& Z6
    7 ^# q( [$ g- p& e7- l; q- s  }& z' r2 X+ t
    8
    - ~4 M: {; N* J% T; e+ A9+ O2 ]5 [& o1 S3 M! f
    100 @; T9 O+ W4 I+ I
    11# Y7 N( K4 R/ `* ]5 v
    12
    - F. ?* \4 O0 Q6 ~7 m1 u13
    * d; f3 K7 y* Y7 H+ |: A% [3 A14
    % G, Y1 Z2 z% ]" Z" p3 a153 g  H& w0 w& H9 c& N
    16
    # t7 o" s" Y) `179 r4 P& b6 ^4 d7 L$ y$ m8 [( g
    188 P2 o6 V7 R/ Y& k5 r
    19
    * P7 U0 V1 m$ `/ k! ~202 Q2 S8 H6 |" y  `5 e3 `# w! I0 i
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。; O% N' j6 i3 v. [; Q- \
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.5 d/ o7 h. E+ I. \$ w9 [0 c% s
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 ! o6 Q2 j& w" Q7 z; W2 o
                                           
      O" a+ o; e" v, N- B5 Presult=result.unstack(1).rename_axis(index={'Date':'Month'},* k! w- R9 ]) M6 l8 u1 ^4 D
                     columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
    ! g9 |6 m" {: t6 Q3 m, w6 X. [7 jresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
    2 h# {0 `7 q" e6 d0 Oresult.head() # 索引名有空再改吧
    2 K4 E  v/ Z: v0 p& i  B# ~# P
    + Y* j0 W5 k0 f7 `/ ^          Week        0        1        2        3        4        5        6
    & T* n3 A. u; L: Y; hFruit Month                                                       
    % D" y5 I3 m# I0 O1 F) x6 j$ }: QApple        1        46        50        50        45        32        42        23
    3 z, n2 I  r5 m- g" qBanana        1        27        29        24        42        36        24        352 A  y5 u5 Q) r; E
    Grape        1        42        75        53        63        36        57        46: n9 ?; x0 r5 u
    Peach        1        67        78        73        88        59        49        72
    ; S* z  T6 m0 C. R5 O9 t! t' BPear        1        39        69        51        54        48        36        40
    8 _/ O5 R& o0 i  e4 R. D3 o4 p1
    6 z. N# E$ u; y% n2
    7 m8 \& R# {# X7 J" Y+ B3 N3 C3
    8 C1 V/ A* Z2 j0 |  H# j7 q4
    % O# |6 p; A* F( ]' }/ A5
    ! n/ q7 d% u' C* t62 X, {( E, o, Z( \. G$ L1 r  p
    7" A4 `( m8 N% \0 F. E6 \
    85 L7 l- t# |/ t; I+ P
    9# ?) ]* }, w! f" ]4 ^% [3 h$ U' P
    10+ h( Y! c* s2 B( e7 I6 P
    11: i: O- P# u6 `& i
    12
    3 G5 D0 [4 f/ _! I4 t13
    8 q( d: e1 n* }* Z+ p) H) a14
    , f8 C: P5 a9 b1 s  Y0 G; i* t15# e# i. P8 ^" m" }/ r; M4 |" J5 f
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    ( I6 q$ {0 H7 \: N0 Y# 工作日苹果销量按日期排序8 |, v) f1 R& C4 c$ Y- I
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()7 |# l6 q; U' }! f' y2 r0 U' u4 I
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总) V( u6 z" T3 u6 z
    select_bday.head()
    ; `# j0 N+ f' s$ f3 ~# Y. I' ?2 `8 y
    Date* A& `, V" {9 @' q: j. g
    2019-01-01    189% ]9 J  _6 i9 K3 F
    2019-01-02    482% e3 F  [( h% U
    2019-01-03    890
    # _& ^; s1 s& x( z; J, W$ o  w( @2019-01-04    550
    % p$ t, R. w- o2019-01-07    494
      q* X& o. Q& o4 s2 d: m
    3 s: h: ]* y% j" l' G# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
    ' P% l$ P+ f: Y* H& Eselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    7 v* \6 [5 A( z+ t2 l, q$ i& Q% v% V) U) r" R
    Date
    + w$ X+ L& D0 y2019-01-01    189.000000
    - {7 n- K# O5 p7 h( _2019-01-02    335.500000
    ; g- [! ^2 k% b9 I. V+ t2019-01-03    520.333333! _$ ?8 [: o; j
    2019-01-04    527.750000
    " B+ a2 G8 N  y- S' ~3 Q3 U2019-01-05    527.750000. B6 q  q! Q2 f1 G

    , R0 ~  g( d$ h- w! a- S( |! d————————————————' q" p% g! d$ J: f" d+ z9 R. Y. ~. U
    版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。' L" f8 @( X& S: x  i
    原文链接:https://blog.csdn.net/qq_56591814/article/details/1266339132 e0 R5 e( L/ N; g) ]% W' P) X/ o
    + ?/ |# Q! q( {. m

    % i' E- z3 z4 H  e7 X) Y
    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-7-30 22:41 , Processed in 1.143585 second(s), 57 queries .

    回顶部