QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2793|回复: 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
    ! p+ s/ s0 x( G, a6 Y
    ; M0 K) f- _" v# R, Q8 I

    % _; e) ]" {* u文章目录3 R4 b" g/ {  U' L, Y
    第八章 文本数据
    " H6 Q9 V8 ]0 m4 s. [" t8.1 str对象) N4 D6 Z1 ]; G- f1 U
    8.1.1 str对象的设计意图
    / Q% t" o5 ?* t7 ~* {8.1.3 string类型
    1 g7 K" i) B8 k5 I5 N8.2 正则表达式基础
    # E3 T$ E: z9 ~" e; i8.2.1 . 一般字符的匹配9 |1 k+ V+ F0 \# M" f) B
    8.2.2 元字符基础2 L( ]! ?! y2 {& Q6 L" f
    8.2.3 简写字符集
    % H  R7 v/ H/ |4 L6 L; T8.3 文本处理的五类操作
    - a9 L2 N. p$ F$ u8.3.1 `str.split `拆分" e! n4 L7 M/ H$ l2 V$ j! N
    8.3.2 `str.join` 或 `str.cat `合并
    8 {5 j; {- X/ b8.3.3 匹配
    ) C' X7 y0 U6 S( U$ X) C/ B4 X8.3.5 提取
    $ S5 U% F4 A6 x3 X- H6 g8.4、常用字符串函数
    % S8 Q2 ]) d# g4 e7 q$ {' S8.4.1 字母型函数
    % ^8 {9 Y& i# j& t8.4.2 数值型函数
    : L: b9 j# j$ Y9 l/ M( i8 s8.4.3 统计型函数
    % F4 [1 V$ S0 h5 L; S8.4.4 格式型函数
    " k/ y6 ]) q5 f" Z8.5 练习1 e, L) J; n0 g+ C. U4 n7 V! ?/ n2 }8 T
    Ex1:房屋信息数据集
    # k: X; D! x+ a0 n! e% jEx2:《权力的游戏》剧本数据集- B  w. `$ ?2 q& `# P% d
    第九章 分类数据
    . f8 B0 f" M9 Z/ [5 e/ ]9.1 cat对象$ E; q7 }+ a; R" f0 |
    9.1.1 cat对象的属性: n4 r' R1 X1 g1 C3 |
    9.1.2 类别的增加、删除和修改
    + ?: n% h- t/ u; V# M% \4 }4 p9.2 有序分类
    $ z0 N1 w. j/ z; ?9.2.1 序的建立
    + H! Q* N0 j9 F* @# ~. Q9.2.2 排序和比较6 ]7 }/ [6 M7 s2 X( c
    9.3 区间类别
    8 ]- H1 {2 k$ a* m9.3.1 利用cut和qcut进行区间构造, A+ R3 s! H# g+ M
    9.3.2 一般区间的构造
    , z' j& s; o* J" e% l" C: t9.3.3 区间的属性与方法  \! G7 J4 O6 w5 p$ d5 i$ `; z
    9.4 练习+ ?4 u5 k- m' C
    Ex1: 统计未出现的类别
    ( ~$ P! K3 ?# M# Q6 VEx2: 钻石数据集
    ) K! }$ N! ?& r5 {# q  X2 q第十章 时序数据
    ; b' `. w6 z+ R0 q" q& k10.1 时序中的基本对象& M% u$ |/ I) }" U; \- Y" |( T6 A
    10.2 时间戳( ^) _. x$ G, _2 q0 g' n2 j* I
    10.2.1 Timestamp的构造与属性
    ( U  Z& K! Q* l( u1 s10.2.2 Datetime序列的生成
    6 ]% q) V6 J6 i$ E10.2.3 dt对象) w4 Z6 _! S1 I* G( K8 Y
    10.2.4 时间戳的切片与索引: T# s" X- m/ {# E- Y. g. @' h( n& U
    10.3 时间差( c" [: B5 ^8 ~
    10.3.1 Timedelta的生成
    1 k  w# z! T# C% K8 P- j10.2.2 Timedelta的运算/ r# _' k* f) w" @) z
    10.4 日期偏置
    " [3 T+ c( C- s+ m& Q: z10.4.1 Offset对象
    . W% Q; N( m1 y5 c; z10.4.2 偏置字符串
    ' Z0 l" l6 v) ]10.5、时序中的滑窗与分组
    ! t. [7 \+ L  Z$ U( s5 q. |10.5.1 滑动窗口3 L) [3 Q8 Q$ a' ~" H) [
    10.5.2 重采样2 u9 N/ r( L, `* l! X2 Q; q
    10.6 练习
    ! g1 L. r4 H' z2 D* F3 g) D+ rEx1:太阳辐射数据集1 N) F3 ]7 e. {, V
    Ex2:水果销量数据集6 x+ w, r" F1 ]! P5 g
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网: E: o+ W6 a6 v% Q/ D# S
    传送门:
    % a6 U4 R% X9 I4 p2 p7 |3 O5 Y! T4 ^( Y% k8 T9 s
    datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
    ; f8 o7 d% k% udatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)3 k9 [$ {! W) O% y) c" ~' e' N8 @
    第八章 文本数据
    ) I- M2 r# v7 u  V8.1 str对象
    % V: L" s4 T0 O4 ~0 p6 R, |8.1.1 str对象的设计意图, i$ I% T6 g* P. j
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    * H, B6 `& Q6 J. {4 o$ e
    6 t8 _+ x2 ^( J5 yvar = 'abcd'& ]5 Y7 p& I2 u* E
    str.upper(var) # Python内置str模块. |  m: x* n/ I( I
    Out[4]: 'ABCD'
    " Z& C+ y) `8 u6 F( E3 N' _& k) ?& k+ T  x" Q6 k7 I- f9 J1 S
    s = pd.Series(['abcd', 'efg', 'hi'])* D9 a; \" {& }* p5 A8 j3 ~; Y4 s( V
    2 w' s9 B$ y; {% k# V
    s.str" f2 ?+ L2 R: Y$ l
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>5 W2 w5 t8 J4 k, v9 k- b" _

    6 S8 B% R1 G/ E! p% {" Ns.str.upper() # pandas中str对象上的upper方法- w" ?! o( V/ J) \$ |
    Out[7]:
    ( N/ Z; w/ Q, {) F$ K0    ABCD
    ' Q" c4 T4 u0 l! y7 L# T( ~1     EFG
    + p8 \0 B4 U; m; y6 z2 K2      HI; T  m5 p% m3 Y. T. G9 _8 m
    dtype: object
      i+ P/ C, [& Z- Y: K1
    $ Z( v2 k" U; {( P. }# y21 x4 b8 N8 c  u2 ?: l. C
    3
    - N, C1 ?, D' L( r! I& R" m4
    ) N" S$ R3 x& o% S* ]* q5$ ?9 {% J/ |: E9 ?: [
    6* L; }0 e! t* p: _7 d! O
    7
    7 x1 K4 @+ m" |+ |) O8 i" J" f0 F8: _! \" n) r! \1 k- J" I# a
    9. O6 J* p" R4 k, J: m
    10% L' r0 W  Z9 ]# E
    113 {3 w9 t* R) ~
    124 q1 y9 T" P6 P2 J  t
    138 {  W' @6 U: g" w) c* @! C/ v+ r
    14& j" B' D0 {+ W4 k$ r
    15
    6 s0 |( ^9 x$ M! Q8.1.2 []索引器" v* |, ]% m  m) ]& K# S  b% ^
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。6 K7 F/ |$ ?5 a4 R6 y% Q1 q
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    6 P1 [5 @; o: I2 `
    " u9 Y* N% m' W9 fs.str[0]7 |) \) G, ?' J. B  b5 Y
    Out[10]:
    ; }0 t1 N' r2 z4 R  f) t; i0    a% j9 H$ r9 ?6 E) M# I3 v
    1    e7 M8 C* A* |+ A
    2    h
    7 X' w3 T& F( \1 u( Vdtype: object
    ; M& T5 K/ p( A! U7 l5 w+ c% ^. X2 B( t" R! U( W
    s.str[-1: 0: -2]
    ' c' s4 K: |8 ?% q4 P$ ^Out[11]:
    7 i4 n) d% l* k8 r) y. Q0    db
    / @# s) e1 O5 N5 \' q4 `1 G1     g
    9 e6 Q# E0 g/ n; |3 d+ ^$ z2     i7 Q, e3 F% U/ z0 v- X
    dtype: object
    7 r8 R* d& ]  k$ d& {. K
    9 ]) W* d( D0 Q0 s) ws.str[2]
    + ]) F( d, h9 C! m- u7 x" i* pOut[12]: 4 L  V# J: F/ r. ~4 `
    0      c
    5 Z( B' [' w8 l/ V9 v1      g$ W7 A; Y+ W& P# m/ v7 D
    2    NaN: e- n3 ~5 ?2 Y4 ], d  j
    dtype: object
    ! n1 o# _- ^! K
    # p: M  ]; B/ E1' ?" o5 V# n6 A
    2
    # F4 v& O+ {4 Q# v  r3 K0 f3 ]  j3
    2 M" G% X4 S2 A/ _$ V4
      }8 C  y3 F/ k2 J) j5) M/ _* R* v8 E
    6
    5 o7 s+ Z, G0 Y4 s% c  P7
    3 G; o3 D- @/ O8$ n) C0 b( M4 w
    9
    ; o+ \. Q8 a' n5 Z1 u$ B. A10
    0 S' {6 Z, c  h4 Z, N% D11( Z! g( c4 _- ~; x. {% E$ Y
    12
    $ Z4 G" F+ S) p; ]5 r13
    ' h; d" Q: ?) ?; H9 L8 H; [5 l14
    ; b9 o% q# Z' I, }9 n. c- ^, w15
    ! C# K* d6 E4 j3 g9 |) Z4 F' x/ ?, ?7 A# X16
    - R5 V0 z" q! K7 p) z) I$ X/ K17: B; Q7 ?# H$ y9 P# A- O
    18# }3 m( \, K7 X- g
    19* B% ]& u. ~5 D- u8 Y* ~$ ~  @1 c5 R' ]
    206 A0 P8 q/ u5 }# |" [: h
    import numpy as np5 k3 `( h9 l$ ?0 `
    import pandas as pd$ X6 b5 \+ Q$ u+ L' x; ]7 q0 |
    ) Z5 q; m( E2 U0 c) K" w- n
    s = pd.Series(['abcd', 'efg', 'hi'])
    . ~& k0 @$ a  `5 ?6 z1 @3 T8 Gs.str[0]* v1 X9 q$ x0 Z8 V3 G# ]9 }/ g
    1
    - v1 I- X& F0 g4 ^1 n2
    8 q7 o' W5 ?% H3
    - @3 h" P9 D$ y  f% ^4
    1 e7 e9 o+ N( {- {; k3 `5
    * S2 }2 y; d% U2 C: t6 B- B0 R0    a" o% B. [) @0 L4 }& x: \8 n$ t. y. L
    1    e8 z/ y! B9 ]9 a2 Z
    2    h  L# |- O) L9 r  e5 T
    dtype: object# R- P! `- b- N2 r
    12 g, }% g1 ^1 d8 u
    2% w$ E$ ^; Q6 o2 V& H
    3% {/ B! [1 ?, `
    4
    ' K: p# w5 m  d6 m' ~- R! @, D8.1.3 string类型
    3 d/ O, q* @4 \$ T2 @. K. _  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    ! y* x9 H9 q- b  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:/ \' U! Z) ]- j0 v0 i: b) n7 X( U
    & q% @: q) k6 u4 m
    二者对于某些对象的 str 序列化方法不同。
    ' ~+ y3 h1 y; B8 ^2 w/ B( K6 M; \可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
    * O6 Q2 E* y& `( I  o% w7 ds = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    2 Q+ q. H1 t$ v* q: H! V; k5 ^s
    6 o/ h! l( O' A1  g+ ~- B5 t9 z# r' K- _
    2" \- R, ~4 r/ z" w+ j+ t2 u
    0    {1: 'temp_1', 2: 'temp_2'}
    6 g* S1 @' w2 K1 _2 r1                        [a, b]5 `! k$ k/ K( t  [1 z) c
    2                           0.57 D( w: ^  P/ @% t9 s: ?2 S+ w
    3                     my_string% J0 ^1 I5 ]8 a3 Y
    dtype: object! S! l  K" i# H
    1
    + L% I. D8 v; q' m( U% j4 t2
    - P" k, P) q  _) C2 v5 ?' X3( E8 k3 G6 V* |2 D8 e
    41 i2 ]% t7 l, }; f
    5: g8 @3 b7 m" p- D+ D" G7 `
    s.str[1] # 对每个元素取[1]的操作
    * Z$ W2 v) Q. o1 C) _2 g1
    ( g% L$ c9 l* Y! B) w% _2 o0    temp_18 ^  E* |. n0 X( m  q
    1         b. `+ c! }0 |" l. R
    2       NaN
    . X% Y' K6 h" v$ ~2 A* h: m3         y% z0 s3 H" |& _! n+ a; R% A; v
    dtype: object
    0 ^3 `! V$ r; ~1 r4 D; x( g1, z: g9 X6 Z( }3 x6 c1 Z# }4 o9 s
    2
    4 [5 l' S5 C% f! b3
    ( t9 I* Y% G% _3 P& f* x4 ]& @4 u4
    8 P$ B$ B8 M( l1 @! u7 B5
    / R( C1 {! Y2 J: L+ Y  U, Vs.astype('string').str[1]
    " E- H6 j+ ]1 u6 K% w9 t0 i- z+ W( P( o' U1! C) _9 D4 }2 t* ]# y) G
    0    1
    ' H6 `7 j1 \1 l0 ^1    '6 b( k8 H# Z1 ^5 l( e' w# X0 V. T
    2    .
    2 f- S5 v/ M* j3    y1 @( N1 n4 C# F; y
    dtype: string
    . T, M* Y/ `( @7 f& ]: {5 p1
    ! C: S2 L- L; q: E% h; H0 i2
    . w% ^7 q( M1 x9 P8 h8 M/ O3
    ) x. ^% D2 Z. S9 C3 d4
    : d6 t2 Y3 z6 O. v0 a6 x9 D' {5# A* m& k# s' }4 {) K7 |0 G
    除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    - y* P$ O7 E! a* ]$ S
    " i3 h# u$ h1 ]! q3 q当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。; W2 ]; b: I/ P8 V- v6 x% ^8 D  I6 m
    string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
    1 }5 I( x8 l# G3 n* Mstring 类型是 Nullable 类型,但 object 不是7 ^; d- X; j+ z- _  @2 W/ R
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。% B1 y: P: E# X9 ?& M/ j, d
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。, v. X6 K+ T6 T- \1 c7 w
    s = pd.Series(['a'])
    2 w0 O" c5 p' y
    # c9 P. i8 e5 S, {4 q' _s.str.len()
    6 a: O7 }" G! m2 f3 s3 F, ~/ ^Out[17]: ; w# i- G) c* m. }- n
    0    1
    8 E, \/ o' l& ]$ o9 Wdtype: int64) q/ I+ J" Z" c( F4 e
    ; q4 r  e0 b( |7 Y5 }
    s.astype('string').str.len()" B2 t% X- r, ^/ x( @6 n+ \3 _
    Out[18]:
    / @9 _0 z$ e) d; }# u0    1
    / Q" n1 }4 K2 t4 Mdtype: Int64/ f' f( g4 U2 `, W  ?3 @2 O7 j
    0 o; n! M; d+ i5 N1 t1 l, S
    s == 'a'
    0 ^- ]1 o5 c* {$ kOut[19]:
    ( p" L# B$ ?! r0    True9 z5 I) w/ A3 u) r
    dtype: bool+ D  c( J; n8 [1 U3 a. V1 g
    , l! f: s7 H7 f. W3 l
    s.astype('string') == 'a'% R3 O) ~" d$ I1 r8 p$ V8 l) P
    Out[20]: 0 ]- j3 l) F6 }' y  N) z
    0    True
    . ]1 c! X4 B: e* Idtype: boolean
    , v# p: l- P5 M, m) Q" L) P' g) e! P6 w
    s = pd.Series(['a', np.nan]) # 带有缺失值! l. P' O$ _& y1 c$ i: g  @

    , W& I  c0 k3 R0 W+ Qs.str.len()
    * q, r  {* O% v6 s; Z# J% ROut[22]: 8 y+ l% [3 M+ t
    0    1.0$ O7 ]  R9 x$ Y
    1    NaN
    2 o: ?* T$ A% O3 k& qdtype: float645 V8 K+ S8 G! L4 F# F0 P

      O8 O8 S/ c$ s: q0 ^s.astype('string').str.len()
    ; F# D7 A1 ^* i9 c, y. {Out[23]:
    ) ?2 g1 \# l7 Z' B0 m) x: g5 ^0       1
      N. O7 n; b" L# e) A9 E1 Z1    <NA>% K2 N6 e" w0 s) V
    dtype: Int64' p9 [# @+ u! S7 m

    ) Q7 n# n! O: Ks == 'a'
      g( T; R2 _7 O7 e: y+ |/ c! qOut[24]:
    # b  I3 E: C+ D! I# j  Q0     True
    & b  p0 ~8 S% k4 j1    False9 Q+ ]$ N; E$ f5 h2 f! q  M% ]2 |* n
    dtype: bool
    + [# f! S' k& X9 H- L& L# F$ u( a3 y9 [7 j0 C
    s.astype('string') == 'a'5 M! B, a; t7 e2 Q/ v3 N
    Out[25]:
    ' @; o+ q+ d7 M$ z7 w* j0    True/ A# {- G6 U9 T8 \  e6 p$ W* s
    1    <NA>
    ' U- o4 q# D( `, {( A2 Gdtype: boolean) L4 z% P2 w4 @3 L- k% ~

    . N4 N% s* V% k3 @1" D. E' E3 k- i( j
    2$ P4 |/ q- F. |6 _0 a
    3
    5 f0 J: \1 E/ o' {4, {( C- E" z+ A; D  ~1 A
    5
    4 }6 K, g2 c3 R6
    ; M" f; j2 p: ]- {+ J; R7) ]5 Z+ q! p( G
    8
    $ }% {! I, @5 R, c9
    : ]+ d0 u8 i5 v3 A; j. F: @+ F0 F# C104 M6 j) [2 H/ d2 Q# Y
    11
    . Y. b/ N" g) o0 g12
    ( z9 p# Y) v. w4 O13
      |- A  M2 n1 Z+ L) I14
    9 p& Q9 o7 m3 I4 Z% ]3 u, G15
    . `7 |1 ?" f4 B& I) r9 ^: z1 K$ ]% i160 [$ U4 s! Y+ Q4 m: @+ `6 |
    175 C9 p3 P; X, I# D3 l8 i' M
    18
    4 k" J2 r/ m% r$ A) O7 D! }' |; ~19) H6 I3 q8 y9 K- o( y% ~
    20' h1 S+ w+ J3 H5 `! U
    21- ?. Z4 w6 g" Y; p
    22
    . @' n! g$ Q  r% v. L23
    . y- k) T0 o: c8 g* x24+ L3 u8 p2 G% I  `
    25
    ( h8 V' O$ f9 ^: X$ k% k+ @26
    " ^# h8 s) _/ _27
    4 D; a8 e/ \$ N28: Q5 W9 v6 v. @* V
    29
    . u: H2 i- {/ O3 ~- x& b0 j30
      Y1 G5 A5 f, G+ b+ Y; l. C31& a4 A7 }$ V0 L6 _& ]( ^7 y3 ~
    329 [0 ]! \- V" K/ k: `6 Z
    338 l! Y2 j# {  I+ m( m- t
    34# @" g3 ?; K+ a; @8 Q
    35
    8 Q7 R1 F6 H  @# C4 u4 b4 ?) x362 n$ P" g; D& k% H2 c+ S8 |
    37' L' `. H! A& [) |
    38. j1 b" V. j# A6 r2 o$ e  y$ g; \
    398 G+ H, z$ w; B; \) S0 @: }9 {
    40$ ~% k% b8 D2 A3 j. m
    41% i: T/ t/ [( m' G" ?9 J
    42
    , m  k. n7 O+ [% U  K" e: S" O5 G43! ?; D8 U/ G( ]: ^
    44: E5 }: r- `: V7 l3 f
    45
    4 I3 m* W; o$ i2 j" \5 b" C46/ W$ K; P& ?6 _4 a7 {
    473 Y+ d, p. p% r0 k8 p& r( x; Z- q
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    & E' {# w! ]( d! n9 V& z
    9 r: f5 Q1 c. }5 g' T, r/ V. r  M, _s = pd.Series([12, 345, 6789])8 [; m5 ~) D8 X7 u2 S  f* {& h

    : {8 _6 P, {5 c4 ]+ b0 M7 }2 Q7 as.astype('string').str[1]
    - s% ]0 P* \5 ]# e# vOut[27]:
    6 T- ?0 y+ T6 M4 n) v: f' f0    23 y' d& r3 E. i" K4 P6 B- d% n
    1    4
    & O! g" o- g. B6 K! V( C- Y2    78 Q. @! b" \+ ~2 [/ R8 p
    dtype: string
    , x0 S; L# F) [1 o- M! h1
    2 k7 x6 O& L+ J" k9 `2& x) B5 J, A; ~8 n3 H7 L* r
    3
    ! k# G! Y: L4 [$ W  j4
    " \; u% C# e4 c* C, W! R6 @57 ?' E+ A: @+ M6 o5 {4 H2 c) B5 M
    6
    " }' ^  I! f; q5 y4 V7
    & m3 u; }" U2 n; ], h/ R8
    6 Y6 z; }1 \& V3 R: R8 V3 @8.2 正则表达式基础4 D# A# N2 r/ @. \9 W- v3 j0 s
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书* r% b) Q  a, f
    3 |5 t; \( Z( h7 [" D' J2 }
    8.2.1 . 一般字符的匹配" I: a6 p2 S: Z( h, j; e' w
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :, I6 U, C- S6 t

    . F) l: o8 y+ r8 Pimport re" T  @  p+ u: O( d
    + Q# h, r+ i. u
    re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配6 w2 |0 Q7 l& K9 E
    Out[29]: ['Apple', 'Apple']' i' ?5 }9 M# I: i( w' o
    1
      ?1 y+ z. ]+ b  J2 c( H2* p$ N$ ?3 H  o  c
    3
    $ ]! n: J' l7 o$ N4
    9 [1 W- W4 J, {" |4 u! @8.2.2 元字符基础
    % }% X0 ?& B- E9 x; n6 P6 P元字符        描述
    : |% [; i- a# X: M0 x) u  C.        匹配除换行符以外的任意字符
    # X5 \* p. |& g) L[ ]        字符类,匹配方括号中包含的任意字符
    3 }8 f& N1 }3 X; Q! A( }& X) p[^ ]        否定字符类,匹配方括号中不包含的任意字符; H1 w# Q6 a* `! x& i1 T% \; l
    *        匹配前面的子表达式零次或多次6 t6 B1 X) v) E& t; A, B. ^/ }$ W
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    8 o* V8 |0 @" [- L8 G/ y/ q?        匹配前面的子表达式零次或一次,非贪婪方式& I; g, j7 F! K; _8 b5 X
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式* c8 o* f% X) X: l, d$ `- n  e
    (xyz)        字符组,按照确切的顺序匹配字符xyz; Z! q# N/ g, U: Z9 }3 n
    |        分支结构,匹配符号之前的字符或后面的字符& g' R; D: i% t
    \        转义符,它可以还原元字符原来的含义: b. k; h8 U3 R" l0 o5 z" {& F3 U! s
    ^        匹配行的开始
    # |& d* X7 E8 L9 i! ]$        匹配行的结束+ E& t, w& q9 Y) y: }* b
    import re
    ( p0 M& r6 i6 R6 Vre.findall(r'.', 'abc')
    - z, f( }+ M& p: N  w6 [# A2 U& B7 z- QOut[30]: ['a', 'b', 'c']; q0 |$ N7 W9 O( a

    ' P/ U' a7 |' l( {- u$ u. xre.findall(r'[ac]', 'abc') # []中有的子串都匹配
    7 q; J9 v' d% k/ |% lOut[31]: ['a', 'c']
    & s9 r% b, I4 h/ R. F& l" z/ o1 o! M0 o( U) {
    re.findall(r'[^ac]', 'abc')
    0 K8 h% _1 N0 @5 BOut[32]: ['b']
    : u! \7 Z) D9 k. ~, s& ^0 v# e( K/ q% P$ V/ ~% j" y
    re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    / |" ]" a% Z7 cOut[33]: ['aa', 'aa', 'bb', 'bb']
    1 _8 Z1 @. X: N9 g$ L6 K6 N5 \% ]8 c/ k% R& n% K+ B- A7 J( ^1 ?1 ~
    re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串3 A$ M6 r; k3 E( g/ }
    Out[34]: ['ca', 'bbc', 'bbc']7 e1 v  j" M  W' M. ^3 O* F

    + M: `6 r  r2 |4 Q0 S# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。: ?- \9 N" P. a* j
    """
    / P) J4 G6 ?+ y4 k* o4 t1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
    % V$ u, M6 d' p- M, u( _2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
    ; y  i( ^! c+ k  v( Y3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
    5 R5 y( l; G: @; d0 L0 ?但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    1 n( ]+ Z3 @; e. N7 Q' _7 n"""7 v+ c) O' p% T8 V

      f! e, T3 L$ h7 g8 k' E, K& Nre.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
    : m- O/ H! L9 u1 F$ F* zOut[35]: ['a', 'a', 'a', 'a']
    % `+ }5 q: ~9 S
    : V$ ?8 `/ _7 D7 @& d' i/ R; b- k# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。8 w. u2 t! a7 o
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。6 s# ]2 r" f# |) |# _% n: [
    re.findall(r'\\', 'aa\a*a')
    ! W: N8 s7 u) Y) d. k[]
    7 V. I4 Z1 \* f) C8 A) ]# Q* i. ?
    9 V  \" D: a% p  Wre.findall(r'a?.', 'abaacadaae')0 l) Z6 Z8 f; l$ n' K$ Z
    Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']/ s2 k" Z1 f: ^( W3 ]3 z

    - k& {/ v# N8 S* [' N$ x4 ]9 Qre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表" y' [' `  e+ O, f* s- {/ q
    [('width', '20'), ('height', '10')]* c! J; G, j$ W& l1 m3 x6 C0 D
    0 ^2 `, v0 p9 z% V
    1
    6 S: ^  e' L( a2
    2 [9 G% H$ l, `3/ A, B. y+ ~7 ^# ^
    4
    ; F: s1 P* x5 a50 l8 f2 x8 l$ H0 x0 S8 |
    6
      b0 W' K# X( J3 K) [7 E7
    $ ]' a# s* q% Z9 A& ~6 g87 d" L. H; K! A
    9
    ! R  m5 J* x8 C+ V) B* l10
    ' @1 g5 q  n+ `4 {11
    ! P' E/ ^4 L! t8 M0 `12% E: w+ X1 ~4 ~% P" k; f) q
    13# k- J9 h) P' a/ X
    14  U$ x1 p5 |! y6 ^: D8 n) `
    15& r1 v+ p7 k% K1 y6 }8 H
    16  P7 Q7 I- r6 I7 x
    17# i1 K. c* B. d; |  i% S3 Z# z
    18
    1 x; l' y# ?) L- p19! E" e& {3 ~3 t# R/ A
    20
    : l" h8 p, C, m7 x( i21
    ( X7 `" P: }: Y22
    % Q; r! h1 p3 `/ f( p23  `: [# a; C  t7 o; q0 x9 \' e7 C6 i
    24" k& @3 {* d/ ?6 k% p" W  K
    25. u# t( }- u. Z, z3 f
    26, o; z! n# z9 y! K! [5 ?8 |
    27  w: b& R' k9 h
    28
    & c; s# d/ A( K  H& `- l29
    7 v3 w" Z" f, p( s. a5 _30
    , J+ u7 I  V. t' S; \- H7 Z, S# X: j311 K* ]1 ]4 k* _( j
    32
    * ~4 P) z4 ?3 Q5 {, i& g% q1 ?331 _# S3 h7 D" H3 ?& v0 I
    347 b5 E+ E/ H+ U4 H' ]: e( A. E% C6 x" W
    352 l/ t2 j% P  p; F; P
    36
    / K  h0 c/ g" q: k9 s1 s  w4 K+ F37
    % D5 c+ I9 O4 @8 ]' G9 g8.2.3 简写字符集8 }$ H0 o* c+ s0 ~! V( ^2 X9 W
    则表达式中还有一类简写字符集,其等价于一组字符的集合:
    * {& R/ ~8 E! Q9 P' h
    8 J& S& ~! w7 a2 ~" ~5 Y简写        描述' ]4 \! V: _# l
    \w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    1 k# B* p) `/ ?2 _) l6 v1 h5 W\W        匹配非字母和数字的字符: [^\w]: v5 k  v# x8 \6 O* K" N3 k
    \d        匹配数字: [0-9]$ r5 @* I9 J: U/ `" d; |+ O
    \D        匹配非数字: [^\d]
    & v- U: L9 q' M/ X) f# Y\s        匹配空格符: [\t\n\f\r\p{Z}]& @1 O; T3 n2 J# M
    \S        匹配非空格符: [^\s]+ e) {; C! \" ^- `: ?6 Y, `
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    4 i7 d& F7 y: w6 V8 E$ q5 Wre.findall(r'.s', 'Apple! This Is an Apple!')
    . w. v4 M8 ?" x3 j# O  F! _Out[37]: ['is', 'Is']' T$ ?- N+ {: I; T

    % Y" G% z& R8 S% B- sre.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次( x, `% }& x' I8 i& `* C* ~/ o
    Out[38]: ['09', '7w', 'c_', '9q']
    ) q. _. ?$ `; }. p7 f1 I3 W$ p3 O8 Y! T- o& T% W
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W). R3 S1 \/ F' n
    Out[39]: ['8?', 'p@']% |" y  n/ y, _3 t+ _$ C, f( e
    0 H& \1 n1 F3 y  a  I+ C* \" I
    re.findall(r'.\s.', 'Constant dropping wears the stone.')4 e" C& V$ G4 \' u/ ]
    Out[40]: ['t d', 'g w', 's t', 'e s']
    9 N; b' J* D( w% M3 F. W* a0 @6 g/ m
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',0 P5 k3 @. }# {$ _
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')5 o1 d9 U* ?, T" p5 ?- f; m
    , r5 d) c2 ^$ o/ d1 w$ u
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    # U/ F% }, G' f
    4 h$ F: T4 R( [1
    / p) s5 R0 X$ g% C% }2
    . s0 U1 L7 L* }0 C3 b, G* }* }3* x- H0 S& F4 C! ^( o
    4- _. f1 s7 O: [- i4 Q% H0 W0 ~
    5# u* K! Y& i# Y+ d2 a
    6
    6 H1 t5 f+ F9 q7 d7" F* I* D" Z" G" L( y) Q
    8
    + n3 A" t: t4 r& a9
    / |& W  F. V) i: O  L# ^102 }% ]7 L' }' U5 Q0 K
    113 G# H& ?6 i: X9 y$ O
    12! B8 G& {! `/ j+ @+ f- e% {
    130 }( ?5 ~* S5 J. g/ ~# L( f8 ]. n
    14+ }0 c3 v6 W1 k, ]
    15
    6 m8 r, H- W4 l% \# Y0 {9 t- _16
    7 S, @( p& _9 i+ J8.3 文本处理的五类操作
      Z% P+ [3 r2 v3 u8.3.1 str.split 拆分: S) }; k* m: {: Q- G$ }% Y! r
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。8 ~9 r# j% `  ?" t* a4 L! J& s

    * s* E0 X9 o/ Q' t3 Fs = pd.Series(['上海市黄浦区方浜中路249号',1 ~8 n6 ]% t  i
                '上海市宝山区密山路5号'])3 u0 K' [% I$ V
    0 r  \) z$ G! \

    $ q8 g1 o" X9 P: i+ Q. C, Z2 S0 gs.str.split('[市区路]') # 每条结果为一行,相当于Series  ~( c  q9 m7 t  V. t1 {
    Out[43]:
    6 H% q1 @0 t4 o( V6 k* \4 @0    [上海, 黄浦, 方浜中, 249号]
    . Z# p% X6 k0 v! x! |6 a9 d1       [上海, 宝山, 密山, 5号]
      w3 u$ S# q, l$ n* qdtype: object6 i5 }: w! m% ^1 R; l4 b

    * @3 n. }3 A% [0 is.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame0 w+ ^( `" f9 v1 b: F4 }
    Out[44]: " @( }4 F9 _  q; I
        0   1         2! U4 B4 i) }- W  q# P* T1 s1 K% |  ?
    0  上海  黄浦  方浜中路249号
      q/ U7 n* f" Q" w! |- R# T3 r3 w/ R9 o1  上海  宝山     密山路5号0 w; p, {/ V' i3 E3 j1 Y5 f" A
    1! p* T5 o5 Q1 v7 [6 W
    22 U( F5 C: c% @/ r' |4 R; I
    38 a) b$ e5 r; V% H
    4
    5 R7 m/ H6 Q7 k/ {  u5
    ) T2 ^' b5 L) r: {6
    ! H( R9 R; \$ t% N' x3 x5 u: N5 Z7
    $ Z7 d7 Q* z$ h2 S0 r8
      T: L; i2 f& ?4 l/ M99 h+ b! W3 M2 M- A6 }
    10' w/ s4 Q' k! X" o2 I
    11/ a- d; L* u: y* ^
    12
    $ m+ y& f* ^* U  n# B13
    " C+ k! e; t  N! X: Q- q3 _4 R14/ r) h  M% g2 T9 T6 m
    15
    2 m# T& a. S7 N( s' y6 U  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:$ x4 a* d* K. X7 B# ]2 n; i
    5 f* I/ b5 N4 ^4 ^2 d2 t
    s.str.rsplit('[市区路]', n=2, expand=True)0 ~: l- s! ^2 {5 h- c7 S
    Out[45]: 0 o4 E7 Z' f. h5 r1 c. ?
                    03 Y) b$ ]* G. Q6 m1 }
    0  上海市黄浦区方浜中路249号
    9 w. w7 }' c8 w* j' p; J9 N2 f1     上海市宝山区密山路5号8 E% ^0 E3 J( h
    1/ ?' w; `7 L0 I' Y
    25 F+ F/ I* o1 I
    3
    % m, g6 L, g, I8 l3 {4 N4: ^! O5 M4 ~- d7 g
    5
    2 T' [. b2 |! u. B' y6 V8.3.2 str.join 或 str.cat 合并
    8 ]; l# f9 F* s) G# `! v; W" tstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。. Y: x$ P7 D2 {" E* D; _/ `
    str.cat 用于合并两个序列,主要参数为:
    8 S* U& s# m; vsep:连接符、( S2 D$ s# [) w+ A
    join:连接形式默认为以索引为键的左连接
    0 ^" s, y  N! w; L, e* una_rep:缺失值替代符号8 g  n8 N3 x6 W- ^  w" [) B
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    # h1 {1 n; n/ _( as.str.join('-')
    * p% _& r* j; oOut[47]: : M4 l) w; r" x: V( t$ \
    0    a-b
    - d* n/ }3 D% i: c* B1    NaN9 a7 O% n; z3 S3 p* ?
    2    NaN5 ~8 A/ L) f: J" F3 t, m! A. L
    dtype: object' g6 A( T3 B0 G) ~5 m1 `. ?
    1$ L4 c" P  e4 m$ Q
    28 c" |* w, Z! \9 {
    35 @, ~+ R/ m7 x" `) S% d9 @* H' Y
    4
    ) C7 x% Y( t2 F' z4 Z  H51 z: \5 P& R/ p3 s3 |3 D7 y; v
    65 n+ b& `) f. v5 M2 F+ x; T' H  J& n
    78 ^: L. H- ]8 Y2 ]7 C6 N/ _
    s1 = pd.Series(['a','b'])  d$ @3 `* |7 p- Y: F- q3 B9 j6 `
    s2 = pd.Series(['cat','dog'])
    * q8 J( T- Y% h$ o: a# H/ zs1.str.cat(s2,sep='-')$ @: B7 `- W8 \8 ]& R8 l
    Out[50]: % c" f' j- `$ ]! x% `0 S
    0    a-cat# ^" V4 f, q/ Z( }! @2 [
    1    b-dog9 [! t: g9 M; T: a
    dtype: object5 [3 K0 t6 D* C0 }' j

    , w( G* T! ^3 ~7 R* os2.index = [1, 2]
    - R: M/ H$ w" u# p+ @, Bs1.str.cat(s2, sep='-', na_rep='?', join='outer')
    0 x: Q# o% U9 b1 [$ nOut[52]:
    & A, ~. ]: f* ~+ j4 H6 V0      a-?
    7 b# K* x9 l, l  {2 t) g1    b-cat
    ' v- l( t/ g. E3 [9 c2    ?-dog; o6 `' j9 ^1 l+ @. E5 q' x
    dtype: object2 f+ u/ J; ]* r  J$ t4 k
    1
    6 x$ k8 z" B- @9 N0 n* b2
    . T) s5 X. T& i1 x* m33 z9 |% Y% F' I1 B* R2 L
    4
    ( B. m% B, d# x+ ?5, h3 ^. m% _7 g! o& T
    66 l! ^3 h& v' ^, A. t( v; ?$ Z
    7
    , ^) Y3 S$ R* o$ N4 Z; `8) e0 ?) U) E/ k" p1 R) H
    9
    / C( v. O7 `/ ~0 T9 G1 q10
    + [, l7 T0 ~8 R5 n" E7 v' }8 F11
    / ~$ m- a$ E! j& t1 k  z; Q12$ \0 s: l  P% T6 d" n
    138 f7 N  d$ D) Z. F
    14
    ' t0 A6 p' y4 \& X" e: g4 L  M2 k15
    / `) V; P- a  z( ]/ v  q; d8.3.3 匹配
    & X' ]# P( ~5 W8 d- \5 Y' ?3 Cstr.contains返回了每个字符串是否包含正则模式的布尔序列:' m6 n; B; ?- b9 s7 c' I2 Q* L- w: }  `
    s = pd.Series(['my cat', 'he is fat', 'railway station'])
    + I6 ?  e% _$ a, T! @/ Y. v+ v: Vs.str.contains('\s\wat')
    ) y/ b# Z( h1 \+ n, Z
    4 q% }" @  a7 T! o$ {( ?: Q) R0     True* A) Q7 X/ \3 T3 N
    1     True6 S( o& E6 F  I3 a
    2    False  P3 ?' b  q0 {9 l
    dtype: bool& f' Z' N& V4 ?2 _4 n/ ^$ S
    1& ], g& a- D- e$ I
    2
      @% ~% q9 r+ l- x- y. l) S* b30 l( l, s% w+ P  V% X9 ?
    4- C7 x0 F% c. n3 k5 |& h
    5# Y* A( E0 A' P4 ]7 [/ }8 t& m
    6
    ; H6 ^  c% q- w0 A& k3 ?2 _7; K) @; Y' o) V5 a* }
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    ; V6 b) B& [1 E( v( ys.str.startswith('my'); l- v/ v' f2 d! T, B

    / ?% _- s: T6 |+ o1 T# c0     True
    - Y8 z8 m2 ]3 s+ ]1    False
    9 h% `: s3 _8 C' @: F* w2    False  C' k# ]# o- [) w
    dtype: bool# W9 f5 i$ h6 M$ }. v
    1
    - B9 o: e- s4 r. J( J2' e) i2 t, b* y4 t3 G0 M
    3
    7 c8 x. o: f. S4
    - `! r  G+ ^4 g# M3 ?, g8 V/ J5; w2 w% r- K! [) Y
    6% k& F  Z3 B6 D9 w" ^0 ?
    s.str.endswith('t')
    ' Q& n2 a# ^9 L7 R& Q5 ^& }* D0 i4 o" P; B; d9 y
    0     True
    1 X4 l, `6 e% ^6 l1     True
    0 m7 |8 i' U! n+ _/ \2    False
    6 N. [$ Z9 ~# Y2 u# l0 Xdtype: bool( i8 f8 B: g0 f1 S; w" d
    1
    4 w9 m! R. W* S$ o/ Y% Q2# S5 l, D" H3 d% j
    3
    . C4 h! d$ T) [8 c* ^+ j& ^4) x' w2 _' o- W, ^! o+ c, U% a8 Z
    5; H4 d& J4 w8 \" L! P
    6
    ! W3 D) M$ t& X5 wstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)6 w2 c4 |" b2 r3 M. U& |! @
    s.str.match('m|h')
    2 V* P$ y  A7 H4 _  l* ks.str.contains('^[m|h]') # 二者等价* M$ P/ u# k! E; H. \: y9 A

    , k! j* w4 ^$ r8 p/ E2 [+ z  @0     True5 Y4 ^5 y, e7 |% w8 F+ {
    1     True. `. x" s; J" K0 Q% \
    2    False
    6 H, x8 r- j2 x9 z; ddtype: bool4 u+ g" o  v( s; e3 {' |/ d4 e
    1& P& x# ^5 g; Z1 T/ r  y/ D
    2
    * }$ M2 r% l5 E: }/ v8 }" r3
    ! f+ x6 K$ J, q) H+ T2 s# p; {- R4
    * p9 `$ v- V. ]! T8 S; b5
    0 `+ F( ?" L  F7 D6
    $ M& m9 @% e9 J5 i; _% S& g9 ~( U/ U7
    # K+ Z$ D& h& A6 }% n6 A; B; _' r' w1 l( ds.str[::-1].str.match('ta[f|g]|n') # 反转后匹配: n! O) i& v6 x% C* X$ ~2 u
    s.str.contains('[f|g]at|n$')       # 二者等价
    # B* t+ \/ Y& O% t1 d
    4 t1 I+ d: n* ^) @0    False7 G4 }. T- Z0 i$ S' l% F# P& I
    1     True
    9 G2 X0 g$ [& [, ?& t' i2     True
    6 O  q! u: o* T/ E7 Kdtype: bool+ y% T) s$ i1 M( k( u' D( F
    1# W! \' ~  ~% ]5 @( ~. Y* Q
    2
    3 [+ C* n! |! v* {/ {3
    ( F$ P* _* x7 u' g* u9 x! s4
    5 o+ E$ X4 [' g  J5
    * x1 A& `3 ?8 O/ X4 w& E; @6# N& `, T8 i& J
    7; K! P, ?0 B5 z; t7 K
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:% ?; N* a& v) x
    s = pd.Series(['This is an apple. That is not an apple.'])
      Q7 n+ S% k+ W- e* P8 @/ W" {) n7 Y; I: j- V) E) j
    s.str.find('apple')- k4 V* j  Z: Q2 q" m8 N6 N
    Out[62]:
    4 R. O' h6 A4 d* b( y0    114 {0 z0 a: k. x- R1 A
    dtype: int64. S: l; l+ [7 f# `

    & L3 H, w, l3 G8 u# hs.str.rfind('apple')
    # S9 @+ o$ ]2 sOut[63]: * }8 a# i- S& y5 _! R
    0    33, L, K) r( r7 g6 K7 w$ v
    dtype: int644 z0 Y8 _8 B3 m9 t( w! e2 T7 P
    1
    ) D; r/ o$ |  D4 \' V4 L% |& S2# f; e! L4 k8 x* }1 g# s/ b. v
    3
    2 g! w, \+ c( S6 s& Z/ j4
    . l$ v  W6 b4 l1 {& k5. `: F, D: k* u9 y/ h. O6 [
    6
    % _' _3 a8 R2 @. \. S4 m$ k  C7
    & Y: R& Q: a+ }# v" L0 _( p8
    3 _1 b# p  q! x6 l) g9 g9, `/ M+ z9 P* x& ~3 Q; W, g8 X
    10
    8 P( J) [0 c( V; C3 S" d8 A9 r4 `11
    % |, N1 N+ n" Z替换
    ' h5 U1 F: b4 f/ X9 Jstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
      X& r5 I- B5 k7 Es = pd.Series(['a_1_b','c_?'])1 g. j) N( E& y. F, [
    # regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    ; [9 n: K) g" cs.str.replace('\d|\?', 'new', regex=True)
    9 V8 T' L( M  Q1 U$ |8 T% ~/ N
    " A* Q9 R2 \5 {1 y2 I+ m4 l+ U" Z: j) X0    a_new_b
    1 Z9 J0 L" ]+ w! [1      c_new
    4 E) p. X6 F" w) U/ Ydtype: object6 ?/ M$ y! A' K) n0 M& M: o+ y
    1
    + I8 X, u/ ~8 ?  K2( @" p* o1 X/ Z& v0 D$ }$ z0 p
    3
    ( Z. W6 [8 R+ }, k0 M2 ?4* i& ]% w  s* O* F1 v7 s- u8 ?7 U0 }
    5! t, Z; }3 `6 P2 [0 j4 T
    6. w. k3 G" y! C4 a- k" v- i0 U
    7
    . q6 ?! E! a  `  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):' k! v& s6 A, N, V

    - w# A( \' v, J0 E  D  x4 F+ hs = pd.Series(['上海市黄浦区方浜中路249号',
    / @9 x+ v( v# H; g, I$ b% K3 g2 e                '上海市宝山区密山路5号',: i9 T9 b% A: a  Z
                    '北京市昌平区北农路2号'])# S4 O, z1 t8 X! [2 L, J
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'4 o9 \9 c2 C) C8 I/ m; x
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    0 Y: {4 j' w" U- d4 c+ i; adistrict = {'昌平区': 'CP District',+ p. a4 Q+ H, ?* m
                '黄浦区': 'HP District',1 E' n$ ]1 X3 }7 K
                '宝山区': 'BS District'}( k7 s# r0 V! i# |1 Z0 t
    road = {'方浜中路': 'Mid Fangbin Road',
    7 X0 J) T' w! O& o, e3 a0 ]        '密山路': 'Mishan Road',
    $ `/ \( k/ r( N4 D4 s- Q        '北农路': 'Beinong Road'}
    : R- ~# z" b! |0 V) x" _5 kdef my_func(m):
    / q' b+ L- E" }    str_city = city[m.group(1)]* _/ _. P5 B& W1 q6 C% o* L, i
        str_district = district[m.group(2)]3 C, x1 A- C7 `4 _6 h5 P
        str_road = road[m.group(3)]
    # J9 e) K9 y: c1 A    str_no = 'No. ' + m.group(4)[:-1]. }! {* |8 |' T0 Z" A& o$ W
        return ' '.join([str_city,( n  _+ m' [3 X8 O+ g
                         str_district,
    ( m$ l% g4 g9 I8 |* q# |                     str_road,
    , G0 \( N5 P( J! J0 t! W9 f8 B" _                     str_no])' Z' a; f: I# Y  K: w# f! ~
    s.str.replace(pat, my_func, regex=True)
    % f6 i8 ]: t6 V. ?  {$ e" n( u% l4 z
    1( c7 I8 B# P2 G0 z: B
    2
    , U' ^% s% B; J, l* S. Z0 l2 t8 q3
    3 X0 Y. ^- x: a" x) F5 E& H; z4
    / Z9 n- U" E+ G/ l9 \5/ `) Q7 w% w# @
    6* M- Z& i) C/ J& ?/ `: |
    7
    9 v4 V! }. A+ M" z# M" Q+ ]9 Y. H80 H( Y3 D$ K) S" }  _' t9 \" U
    9
    ! d6 e+ d: F, l4 z5 G( A  }10
    ) l' U; A4 `+ @, @( s. p11
    ) B. k5 L& Q) b) G* Z0 |12
    / b1 f. m8 H3 U9 Q3 A13; k: k; n1 A4 }5 t' }  i
    14
    . ]  P' ^; c) }$ C15
    . {# I2 p( s# g: ?1 K; o16
    ! }, q" U8 R0 A9 S6 `17
    3 X5 k! w! _4 I* c' H18
    $ G6 w, `7 H6 b19: T% F; d% o! c% C! g, Q, e
    20) u+ b) f( L# ]# q4 W
    21
    : a; Z, f3 m2 x3 E& k( S4 C0    Shanghai HP District Mid Fangbin Road No. 2496 E* U# b' N: J( E( D
    1           Shanghai BS District Mishan Road No. 5! e; X9 ^5 q" d+ A- X' `
    2           Beijing CP District Beinong Road No. 2# ~- x* p  a/ b+ C) P9 \$ o
    dtype: object
    . |" I, D8 ^* |5 w3 f( d, I. F1
    % I3 U2 M( N6 w  D, N- W' l2
    ! y' a. G, L+ W3 ~9 ]1 @/ x3
    2 x6 Y) j* H* W# N: \* j4* N) v- {. ^# e; e$ ~
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:9 e$ _( C7 Y% r6 Y0 O( Z% n
    0 S8 y" M2 y" u3 ^9 \1 V( ?" |) n
    # 将各个子组进行命名
    . H, z1 |/ \, X& D2 D/ ~pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    , F  n; `6 t+ i4 sdef my_func(m):
      T! d) z( l: v8 W% {    str_city = city[m.group('市名')]
    & b' V" T. S2 c" t    str_district = district[m.group('区名')]
    ( K8 W; D9 {' ~! U9 z% L    str_road = road[m.group('路名')]* s* P% E% V/ I% h' g& \+ U4 d  e1 f
        str_no = 'No. ' + m.group('编号')[:-1]5 Q8 K% |% e) q! O5 K
        return ' '.join([str_city,( h) q+ a! B0 N/ E5 Q$ w& M: b
                         str_district,, g0 s1 g( Z. O8 z
                         str_road,
    2 P  w, K$ |, g3 ]' I3 o* J                     str_no])
    ( n. {! {  K9 G( is.str.replace(pat, my_func, regex=True)
    $ \: k) |: b: w3 o& {% b( e1) s( H7 k+ l! ]0 h$ w. t
    2  s: }3 P& A0 L" ]& ~" V; Q8 q
    3) l/ k$ h: I8 g# q& ~/ R* h5 [) b" ^% N
    4: T  @8 `& [) ?$ y- b
    5* {) [  Q! l- ~8 C3 N' h2 b
    6
    " b8 a' K1 @7 u9 e7* f5 F: ?( c5 N# M$ ~
    8
    % x/ ?, b% R% S# H, Z* n6 t9 ^$ l  f9
    ) {( O/ l# I7 @/ f& q4 `, W) G5 b10! p3 j$ d; B, M" a* r' `9 d
    11, Z$ F4 B4 X. r7 b
    120 u0 M) L* c+ ^+ W2 k6 O3 s& s
    0    Shanghai HP District Mid Fangbin Road No. 2493 U8 X4 W! A8 Q3 q
    1           Shanghai BS District Mishan Road No. 5
    ! m) Z* q/ s6 C; H0 y: F) b2           Beijing CP District Beinong Road No. 26 U. |  r* K( \+ r9 m
    dtype: object3 z- i, [$ W1 q1 z+ ^
    1
    ( x4 M0 w1 e8 }5 Q2" [& y- e7 C, n5 M9 |" d6 d
    3
    # u( [6 n! J/ `) C- G41 L& k$ s# M! x" E
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。7 ^- O+ K/ y/ X9 \5 z. x# E: I
    ; X0 v4 h9 p6 X5 [0 m" R
    8.3.5 提取
    " j- O8 \: j, _str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    ; f) |+ m' Z7 s+ E5 ts.str.split('[市区路]'), `& Z, `2 p+ k: Z. q. [6 I
    Out[43]:
    ; n- @, ?  I; j  U1 h: p+ o0    [上海, 黄浦, 方浜中, 249号]
    - x+ ~$ M2 z2 [# a2 m/ V1       [上海, 宝山, 密山, 5号]
    . q' ~  z! z. M/ ~& x5 l$ ]dtype: object& x% u# ~  I% W7 {# T! q

    4 `' K! Q& i5 _- |# E2 s6 mpat = '(\w+市)(\w+区)(\w+路)(\d+号)'0 F6 p8 w( |8 ^4 J7 r) n
    s.str.extract(pat)
    + x; E: K$ B+ e5 n% POut[78]:
    * k* J( T) B( N# B0 ]5 M6 m    0    1     2     3
    7 p) P( Q" S: ?8 ?; h0 z/ k1 J5 ~0  上海市  黄浦区  方浜中路  249号( r5 B' `  u* G$ T  N8 M
    1  上海市  宝山区   密山路    5号$ O3 q, ]" E; p" _$ ]
    2  北京市  昌平区   北农路    2号1 `( J+ P7 P: M1 I6 W( d* b
    1+ e  p1 g* Y# `! [$ J9 f' s$ W
    2# O2 Y7 e, @3 {; A, [
    3; }$ a$ y; T) H2 z* e' ^" z
    4
    7 |2 i9 I- N& N/ f, Y% l8 _3 I6 Z5* Z4 v& [( K! ~5 b, [/ `
    6
    1 V% m1 R# Y3 m" e' M9 W6 d7( H! |( r4 m: h7 a. y7 _% `7 G
    8
    2 D3 E# y, Q9 ~0 }* x. ?, u9
    : E  x  Y$ `$ T) Z* E6 c* H- B; g4 s10
    ) g4 C7 B& T) k2 U; s11
    9 O, \# @2 u: _# j& Y; N  i12
    ' L' h" q" t; N) s; U/ _+ k13
    . S3 r4 m6 l4 m3 ^2 t# j通过子组的命名,可以直接对新生成DataFrame的列命名:
    : P- C$ a0 u1 n6 ?( N  p. y- a# i- H' e' p" g
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    ! d4 h( I2 t& ~7 k' T3 e$ _s.str.extract(pat)
    $ z( i/ o# U4 Y: u8 u/ WOut[79]:
    4 K% N  b! X4 I* u, G    市名   区名    路名    编号& o9 U# d- K/ _0 d% k% a- j
    0  上海市  黄浦区  方浜中路  249号
    3 \- E: r6 `7 c5 ?4 ?+ S1 B1  上海市  宝山区   密山路    5号
    * t' D: n- w, C; `! V2  北京市  昌平区   北农路    2号) `, S3 Q) e' U. @
    12 @* W: M0 k. i, n9 b0 L+ m- R
    2
    * F8 j* w' A* h. t* H( n3
    * {1 }- a3 h- g" N/ p' D2 F4 l7 R4( ~& w: \" {6 N0 R$ u4 Q6 h
    5. J0 p6 n" ?4 R# e4 B* m5 r5 ~
    6' o0 T% T1 R, U( k, u0 u
    72 A6 g- ]8 X) }: S' o* ~
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:6 r9 Z2 I2 d" c; P
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    & G+ ]7 k* P' m) `8 m) l* {pat = '[A|B](\d+)[T|S](\d+)', E5 y3 B4 M( a# m
    s.str.extractall(pat)
    0 G& ]# b7 I4 k. v0 cOut[83]:- V  J: E' c0 y# e! A
           0   1$ J7 n! M1 ?  O( Z
         match         
    3 k( W0 N1 e4 e& z/ D% ~my_A 0      135  157 x( s& G' V& n6 I: N% @7 O/ O/ J
         1       26   5
    4 x8 L3 ^" }- Umy_B 0      674   21 w' w% [! S) @, J4 g
         1       25   6
    ! L# s9 ~2 o2 V; p1  q5 E9 C9 B1 N% \3 P
    2
    % Z) u# n  U4 g3
    5 y, G" j" Y# U- {5 @4$ u8 T4 Q* ]% n% b& K
    5
    - ]# L! d# C0 K, ~; @- K6
    2 M6 P- f/ R  x' C) N& u7% s2 `/ S2 L$ Q, x2 ]
    86 t# n. T* _, L) M
    9. ~' Z# w3 R. ]/ I& ]( q* y4 X
    10) O( ?& T' |5 h" |9 p+ o. O
    pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    : A7 k9 v& E9 ?  Ws.str.extractall(pat_with_name)
    9 |$ R  _( l, c% Q6 T$ F$ GOut[84]:
    9 |6 i, Y% h: q6 K% {% K" }           name1 name2
    1 ?) m/ s- l5 |+ ~4 W6 d: i3 k     match            & S0 D) `" R. @+ a& I6 ^! {5 p3 C4 z
    my_A 0       135    15" q3 |' Y" l$ C* W9 o( M
         1        26     5
    1 L4 B; ~# t1 e- t" Qmy_B 0       674     2
    8 h5 l: x3 T) y6 g1 N( @+ f+ X  p& Y     1        25     65 P' Q6 p" K8 s& _  S/ u
    1
    1 N; }+ h' p, w4 g1 x2
    ' q- ^' L# m# ?( T3
    & N; q) p- T- b+ C9 k1 n4# o- T! E4 G$ _, }
    5
    0 M- L7 Y) `0 D% ?* D' P6
    9 @& y1 r  Z, p; K  B' |& F; V2 ]4 y7 ^7
    9 }- o  V5 D9 d; Q8
    , `# c4 M+ S) J9 D3 r- N; `9
    3 ~0 `( H# O" o$ b9 ?  A5 B8 vstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。! c/ g6 \: o) s. U
    s.str.findall(pat)0 M5 n! G7 c8 S) U4 ?
    14 U$ U" P5 U3 r4 u. |7 N' L" l
    my_A    [(135, 15), (26, 5)]
    # {2 Q' Z7 n7 t: l& \' Z. x4 i5 jmy_B     [(674, 2), (25, 6)]
    - u8 O8 a0 M/ g4 L; z1 \$ W+ [dtype: object
    * T9 w; L' h9 v9 K. c1
    ; g1 d9 Y2 g3 k2
    6 ^( t1 l, L, d# N0 t3
    . G! ~) |2 o$ F* n0 Z9 d8.4、常用字符串函数
    ( K/ c' [' B! j& F- ?( R; i8 M  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。9 J% B" b4 N" Z
    7 H) m  `8 y5 v3 h. Y% `' R
    8.4.1 字母型函数3 I3 f  ^- o0 W$ M% Y, a
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:  D/ t0 m+ \  ^0 a: s

    % ]& f# T" f7 z4 g  q/ ms = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    9 u. D0 P; q8 v' u' P( b; u
    0 P% t0 @; `0 y$ _  ?7 k' Ms.str.upper()% ~1 A- e( @5 e/ i3 B/ n' L: V
    Out[87]:   ~/ b' P9 S; @2 s
    0                 LOWER
    $ [6 S' R. O- d4 B8 R1              CAPITALS
    1 Z4 |5 O2 F0 i2    THIS IS A SENTENCE" e# k5 ^) C7 X* [
    3              SWAPCASE" a7 {; L( n) y$ b
    dtype: object
    % q1 G# o% c' g) a" g: m) d8 \, Z3 Y" @( D
    s.str.lower()
    " G+ s% d6 q8 \/ w4 f9 rOut[88]:
    6 g. h( i" X- b0                 lower
    5 @6 _& F# C* a: M2 h* I1              capitals
    + M+ S7 W0 N$ k; r& `2    this is a sentence- j' c7 Y' _5 `
    3              swapcase
    0 \: t; X" [" ^! S7 O4 Sdtype: object" H! A' B3 c- J' ^* `

    ( e' I# O' p: ^# m. ?1 ?: Os.str.title()  # 首字母大写! Z) ~' M& N# \) e9 o6 C: U
    Out[89]:
    , J) `" Z9 U4 F8 @. u  S, `- P6 u0                 Lower
    0 j2 v/ m7 j# e1              Capitals: E- Y  ^1 _5 h" J
    2    This Is A Sentence; u5 H5 B; C# v- q& v7 U
    3              Swapcase, I! D, _" F# K0 {
    dtype: object. n) |5 C' T9 W2 k- \, _# K
    ( N- `4 p, }' U
    s.str.capitalize()  # 句首大写
    1 u/ Q: v% m% w. T2 COut[90]:
    , m" B7 q# ], A& H4 S3 b0                 Lower
    / w# F- E! x# L, `( I2 V1              Capitals3 G1 N# N" M/ p2 q# \
    2    This is a sentence
    " s9 z3 J1 H' b  L6 {$ e! ~3 P* ^3              Swapcase
    , Q* |% M" [; n% Z8 k3 ^9 V- V7 Sdtype: object9 S4 x% y; G$ m' e# X& }/ R
    " u& [2 A2 m2 y
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    + c! Y2 X6 v$ ^4 A! WOut[91]:
    % K8 z" x% y1 `( P" X2 A/ u! k% Q0                 LOWER
    , G1 c0 E: N3 X' ~0 r1 W1              capitals+ x9 L% ?/ C  C, m" i1 v. l
    2    THIS IS A SENTENCE
    " l1 `! A2 l. h+ h3              sWaPcAsE+ @0 z3 S' z- G& _$ u; S
    dtype: object
    7 l7 O( N- C1 G4 i! e/ J
    $ w) P3 {: M5 Ss.str.casefold()  # 去除字符串中所有大小写区别9 [$ p0 g1 O& `9 ~7 X& v
      b2 D/ I% b  C' U
    0                 lower
    # j# w1 l0 A  `- V- U2 {3 |, b1 u' i1              capitals& V0 A) L& I' D- K. B& E! S' `1 H' e  D
    2    this is a sentence
    6 N) L5 r# \$ `* z' {6 r' g* Q) a' r3              swapcase4 B5 O% `7 N) X, C( P

    & n& Z/ N2 J5 h% C1 Y$ @1
    9 E' I) g0 P8 a# y/ s* `2
    $ w( D6 w" Q- q+ A# [9 ^+ E* r3
    . e9 Y% l0 p* S+ ^5 ?4  I1 c1 ~& L3 v3 S
    5
    4 \, f3 c" ]6 ~$ e2 p61 G4 ?' a, _6 l* f, p5 n
    7" ]. D" E+ e2 m4 r1 Y% O" [% a
    8  {5 ^: L( {: r5 n* Y
    9
    / v: u# s% W* e) m; K( I- O10: w5 _! q' i% g
    11, a6 l% |2 N8 ]. x
    12
    4 @+ c' }  m, ~3 G0 j! A13+ i! d% g  z+ _( m0 w
    14
    ' E" g3 ?+ W/ w( d% w( b& g15
      C; v! p- @5 Y4 N4 k5 a6 Y0 i5 L7 i16/ P! R/ J; r2 b7 V6 k& f- [
    17
    3 q( c4 R8 s) e18% K, V5 C( h! H: a: F
    19
    7 I  u2 O  P( F2 E6 s2 B20
    4 y! o$ h1 K/ E5 B# C218 }+ E* _; k4 f1 l% ^$ a4 a" F
    22, Z& `' n3 ~; x, O! V* |8 E
    23
    6 W0 Q9 E9 |/ |% E0 ]24
    & B6 U: ~( c6 ^/ V251 C) }8 Z3 ^  e/ v. F
    26
    - F* I; B2 X% F27
    : _" u$ J  x; n) u- q4 Z28' U3 W* K. O1 ?& }9 h8 a$ s
    29
    5 D3 w- f- Q0 t8 a30
    2 O+ H5 U3 y7 A4 B31
    % x1 [- M/ K$ W' O6 ?9 r32
    # c0 q" R7 l* w( x7 P- g  b0 E33
    ) d6 }" A# y" u34
    ( T# B6 F1 d& u, @/ R6 J% y35
    ! x, S+ _" |! f# P" M36. z6 M3 O$ M& @3 M+ m
    37! u1 y0 F5 k: K/ w! f. x! G
    38
    5 N; \4 T2 O5 p1 {6 x39
    1 a* Z# Q4 S3 @  s% u9 e40
    $ c, n: L1 z* j) z" j; H$ E41
    4 p- o* Z: F: J# m427 H6 g4 b- A* `# j5 C1 o
    43/ m! t" U* s4 j5 ?- w, t
    44
      K  s, r" U* e8 s4 A- x" x45( B6 A) x, {1 M+ j
    46' y( t- b1 a# S! l
    47
    4 a+ z3 f4 {0 j48
    . j) G3 S6 [  M9 {8.4.2 数值型函数' M* h1 e1 J" p
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    - e, M7 e* g) [% k- N- d# ?% |% D' I8 }9 b' ^* X2 R2 \1 Q) K  h6 p
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
    : ?6 l  b, a/ M( W% \raise:直接报错,默认选项4 F; b$ \( P6 Q' G
    coerce:设为缺失值
    $ j. W6 U# n$ _3 o0 i4 ]1 rignore:保持原来的字符串。
    " q$ {1 F" c4 idowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。  t* ]% Y; r. _8 i9 ^  N: d* }
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])  ^! ~' L# |7 ^
    ; a: T& l  ]; h! B- t& ?
    pd.to_numeric(s, errors='ignore')
    6 @2 {1 C' k2 |( dOut[93]:
    3 t" H- O+ I/ U* d6 e3 ^7 \8 r0       1
    $ k. S$ N5 T! H2 ^1     2.2
    * P8 V6 O* p6 [/ y+ h/ `4 R3 x" J2      2e0 ]5 |& `3 V) h* q! c
    3      ??
    8 b7 d$ F1 T+ |8 O0 a( `8 A4    -2.14 f$ d. m/ N5 n3 f! W
    5       0
    & q$ j. {* l, Y& q# N8 @- odtype: object
    3 f/ C/ a( ^& B; q9 U
    2 n' h: @( S/ M, Z/ m7 rpd.to_numeric(s, errors='coerce')0 x9 g: }4 ]9 w; ?& {, V! P# V
    Out[94]: ' F  {3 F5 @! U' A9 m9 u2 ^, R
    0    1.0
    , p( m' H# ~% C6 p" R; W1    2.28 B. F* u1 }+ r/ w7 Z
    2    NaN
    # G8 w; P; t0 l% |3    NaN! b" x! j+ P" [
    4   -2.1
    ! {* K- ^5 r1 ]3 r9 d5    0.0
    5 x* V5 P6 k* @# w7 [& ldtype: float64
    * t7 l3 ?' m1 b: T2 y$ K" d: h
    8 |' x; j3 m* Z: P, s1
    & Y6 |6 ]: \, A: K4 [2
    7 i1 w$ O+ I: h4 f6 E: U1 ~8 w3
    . P& A8 n* G5 t  v' ?2 F6 M, g4 s! v% e4
    ; r  E$ A$ @( b& }5 u5 _) C& R53 M, i6 O$ K& F( \  ~9 G
    6
    9 y. Q7 G' J$ o# S7
    1 w- o# W6 C4 U$ C: S0 E85 h& j7 M6 ~- B, y
    90 x3 c0 Y& ^! |$ |1 T1 v
    10
    * P" V* `3 J, ]2 g11( O: W; |+ y$ x3 }7 @  g
    12  l" \; X% Z* {6 k. E- F# z( m5 A; Z
    13
    9 u" u; z  H! C5 i% Z8 k, @  t14
    ! q) E: s) ?+ f15# H3 R. W' H0 t* `) H
    16
    1 L: C( I4 L( Q0 @1 `/ x% `2 p17% o+ P) c+ o" A, S. g, K
    187 y6 P  M. U: p
    19
    4 o4 y7 ~7 t5 P20! M! `+ s3 N% `: Q
    21  _. f! k9 V8 ~% y8 x# M1 h
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:# D# t. A$ a- n9 {* b- W) n5 Y- t

    5 T5 w& z& d6 f6 b' |7 ?# is[pd.to_numeric(s, errors='coerce').isna()]# Z4 i' \3 G5 W, r: o
    Out[95]: & \) ?: I4 z5 t
    2    2e
    9 Y( ^. n+ W. G) J# `: i5 B3    ??
    * U6 e& Q% v( X  Z0 k+ V+ Zdtype: object
    7 B0 O, w, A% ?0 `. w8 K* @7 _1* o  l: s3 V0 ^( ^, W0 k4 U$ O
    2
    " j) \: h$ [1 n' |0 t# m6 {3( p$ v' C, J$ ~2 z
    4
    2 U+ E# p( z7 D1 s5; D" O  f; C8 e0 s  d
    8.4.3 统计型函数5 v: v) o5 A9 s6 z/ O$ X( Z
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    " [4 P- I; X/ t7 Z  d) s; e) C3 j+ _
    # D" o1 q# g( W% k! P$ as = pd.Series(['cat rat fat at', 'get feed sheet heat'])' N" O+ q+ g& J+ V* I. r$ ^

    1 q; C& S& `/ ^s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次& ]. G. |1 Z  @% h( Z
    Out[97]:
    / m4 M4 b, o' W' o9 a3 j+ l0    2
    5 Z% N, [- t- {% `1    2
    ! a% _3 [0 x- Y( _/ z* @/ Hdtype: int64
    1 q5 C$ U) B, Z7 g9 t# n1 c0 R: e% P+ O: [& A9 I- Y
    s.str.len()
    ! B0 c7 q' e; q- bOut[98]: 2 J2 y" x; d" ?: o* m* f1 g5 o! |  @
    0    14* B( K0 h3 D6 E
    1    19
    2 e; k. c& i$ T6 R5 M$ idtype: int64* A6 P' t4 p) R( A' c
    1, k& \* K) o/ J, f/ W  b
    2
    9 O6 z, w; P# U+ J3
    . ]2 R; `: E& C" K, N4/ z) d. F- y" @5 ]% o8 }: V1 @/ o
    5
    ! u' V+ X8 i- z+ ~2 }, o9 @9 s  B6* c. m2 K& i2 O, P% d; s; R& _
    7
    2 r" I+ [& {; t/ |8& m$ R  M8 w8 d9 f
    9+ H7 t* ]5 Q: z% ~/ {
    10
    , n4 J' C" ?" s5 P11
    ) p+ K5 W: O* Q8 o12
    7 u: W  B+ Y" ^" c13- v2 A" \" {6 C% i3 E4 @
    8.4.4 格式型函数7 q% Z2 A0 h& U" t7 ~& w# Z6 a
      格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
    ( U' s( w( Z1 ?) l; Q
    2 u6 v5 Q' K! G9 zmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
    ; y$ C7 j* c  W8 P6 O9 Y9 M1 L% W0 v- F3 d- c6 F
    my_index.str.strip().str.len(). G: d0 P+ j: v; O' h
    Out[100]: Int64Index([4, 4, 4], dtype='int64')" t" C3 F/ a2 \" }( U+ W# y: \! _9 _2 C: x

    . Y& f) N0 Y# u9 |/ C% omy_index.str.rstrip().str.len()2 |% k0 y1 V& a! [1 R
    Out[101]: Int64Index([5, 4, 5], dtype='int64')
    + u$ ^1 H1 x0 i# U
    ) _, |% e  I2 x7 d7 ]my_index.str.lstrip().str.len()$ T+ v9 F& Y4 I- i: ~9 z$ x7 g
    Out[102]: Int64Index([4, 5, 5], dtype='int64')
    ' d5 s( }9 i; p! f) T, |* d& K1& H# j3 I9 D- g, i
    2
    $ e% `, ^9 y( e3
    1 l, G7 x) w) E- A# w4. e8 }5 L$ O; H4 i! J2 p
    5
    ( h# q) B3 x2 s$ A6
    9 w! K8 @' u! t: Z( d6 t/ ]7
    9 q# l) e- `$ X4 j+ y* z4 G1 _8% i- Q8 g, u& z7 H: [6 H$ [3 |
    9) q' o* ]- _8 W8 {3 C# o3 q7 f
    10
    . u- `5 ^" X8 }2 Q0 E0 I) Y  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    9 M0 a2 s  V/ D! \3 |# z3 _8 a; V( E6 A) T. z8 Y, t0 p
    s = pd.Series(['a','b','c'])
    : e& s& i6 S2 U$ D
    # v: r3 B- X: w! e) S9 ?" Ns.str.pad(5,'left','*')  ?6 `* _, ~( |/ B' {  e" ]) N) r
    Out[104]: 0 j" F% M# q) t$ X3 V
    0    ****a
      v- E% x+ V9 z# |7 n2 Z8 t# O1    ****b
    / m/ [) O6 e  s, F9 T; V+ F2    ****c
    % \+ o( I- w! j# v$ w9 ldtype: object
    8 k3 R3 T. s3 V6 W0 l) C* Y. u6 t8 }' N7 X8 {7 m: Y
    s.str.pad(5,'right','*')4 I+ v9 _8 x, r4 B
    Out[105]: 4 p! n! B, K4 U2 K  i' t
    0    a****0 K$ [4 @1 u+ c! B% m) q# Y  {/ w
    1    b****
    / ?+ Q; n. T2 @& S2    c****2 Z. H/ f% Q: a) n2 k6 V. S
    dtype: object
    - s" f4 v+ I( {4 v5 [8 z; o
    0 j' j# P$ I( W0 V! us.str.pad(5,'both','*')
    0 j( n6 V1 o! h. {6 nOut[106]: ; o7 h& v! z2 k, K% D8 a
    0    **a**2 K+ l' k% b- }+ _" `* ?+ W) l
    1    **b**
    " P4 D& u1 q) i2    **c**5 M0 I+ l1 H3 ~
    dtype: object+ Q% x$ d+ b& z
    + `, d! V! a' H
    1
    6 d( S1 N1 S* H9 ^) q29 y" ^$ s$ p$ T+ H( l
    3
    9 `$ O" l. Z& D3 v, t; o47 G5 T1 ?% g6 O# _
    5
    - t" K6 ~, N; m( X$ U! R- N6
    7 l8 |9 s' G; ?/ D: c7! [; Z$ Z% j2 Q2 W, x
    8
    8 m6 ]3 k- i$ c$ P9, X; ?8 r" }1 |& a' U* A; X; n4 F
    10
    # ]0 A. r  E) U3 n% X* y11
    ' S9 ^8 k$ q$ u& g; k7 Y4 U12% l) A* Y* Z2 o( P3 N+ |
    13
    % E4 h/ \( c3 U7 D$ B4 y14+ ]4 {1 C. R/ b+ ]1 u3 j" J& U
    15- I$ B1 M6 ?5 {7 e0 L- K+ }* W
    16; A  q8 h# g' O9 B: C
    174 ^! a. b2 v' S/ M
    18* c6 D& b# I+ U  l
    197 h  }9 w7 y* H
    205 ]% o. D% a9 m* G7 v( i5 u4 S
    21
    0 a9 |' q8 Z- U22. D8 N% j/ H( D/ A
      上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:3 P; K3 {' S; d2 u' I; Z+ a

    , L# ^' G. _) _4 U% C4 J+ r# }s.str.rjust(5, '*')7 P% ?0 g9 i4 V/ a9 F
    Out[107]: + {' c: k- A; q7 p9 e7 s- i
    0    ****a
    4 K: i$ E" K. @# d# V6 t1    ****b- O8 y- k5 o' g# c
    2    ****c/ Y  z. |! a, a1 |" S
    dtype: object
    7 T& [5 O5 y+ E5 Q+ P" i( n3 O5 C
    ' K$ u& |; c- @3 d, R4 Gs.str.ljust(5, '*')
    ( I9 e3 z  T# ?8 [Out[108]:
    ' [# Z+ m4 ?7 ], |, N6 [9 {6 j- W! N+ ^1 r0    a****6 B  a0 \1 |9 j; h) r# o
    1    b****
    , ~' U8 p  t" ^  P4 S1 {$ X/ n2    c****
    ; B) Z+ `/ O) Qdtype: object9 q8 ]  _, M8 c2 T/ x# D! P

    0 O  W! g) G* J. Ss.str.center(5, '*')! c; ?3 G$ s6 Y5 v: z
    Out[109]:
    ( e( v! y8 f0 |) \( P1 E0    **a**! {8 W- S$ d/ b$ p0 J
    1    **b**
    6 r; K, |8 f& l/ T& H2    **c**8 n/ |) K) n9 ?9 D# Z0 p" T
    dtype: object+ l0 \; K! c! R, T; H

    ! z: G5 m' ^+ E7 s" L- l- X5 A/ g1
    4 k* U8 V% C$ ]2: ]' c, T( p7 Z. v
    3$ ]. l. S) G; s% J" d
    4. w/ K( s/ ?) m. M) T* m  ]
    5
    1 P  Q8 {4 D9 T6
    $ A7 F9 d: u3 M. r: @1 `; H9 m4 |7+ T& a: L: i( \# W. b% K
    8% ^5 E% I: c2 z, |' w- I  M7 r
    9' g4 q& O; {+ R) H( X
    10+ B: m! b' Q6 C! u- p2 }' k
    11
    ( E/ c  B* ^( {6 @% E' v5 R12
    ( [8 R( f7 w3 Y0 D7 V) a- D13
    % T; c, v" V' N! Q* o, K145 y! E+ T" u. Q* R" l/ B
    15
    6 A+ Q; ^( m9 F16
    + q/ q1 J( L- L1 {17
    2 d9 F, Q6 N4 z& Z5 |4 X7 z18
    . ^) p9 ]7 u7 x. B; U# ~19
    2 C. T. e& b' P- n20) ]4 [' y! C7 b+ n
      在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    0 T5 K- L; F* c! s% c8 `# i" h7 c: g7 o7 L+ t5 q! q% w
    s = pd.Series([7, 155, 303000]).astype('string')
    5 a1 `1 l1 r6 _( A1 k8 Q- l0 g* U& k# ]8 {) n" ]- D) Q
    s.str.pad(6,'left','0')* _! n3 F- J; C' m
    Out[111]:
    . O4 u- P9 ]  y0    000007
    ( s+ ^, k& J) N& c3 v& @5 S1    000155" U3 l6 N5 r/ v
    2    303000, K, [6 B' {( T, D0 I
    dtype: string
    & p/ b" u: |( v2 i/ k* ~# X: Y: q
      _! A. N- d5 r8 h& Q5 b/ ts.str.rjust(6,'0')
    9 O) X7 f; j& mOut[112]:
    1 h8 z; d: m5 U$ o/ c" p0    0000070 Y. t' b9 F, ?/ c9 O$ A
    1    000155
    5 h. F2 y! S: r' }) }  Y2    303000
    & O5 J- [8 X: A* Z; d( tdtype: string8 S$ {4 c8 u9 `, U) q
    3 \- Q! f+ ?6 z& R4 Y8 e2 i
    s.str.zfill(6)* Z4 Z" w# V% l2 K1 j. _& H
    Out[113]: 7 Z$ a" |7 r5 `, M  G
    0    000007
    5 G% b, y. b: B% {1    0001554 p& x1 b( {" u/ p
    2    3030006 Q2 l8 Q  a7 C! `
    dtype: string: u* m; [2 Y, q
    # f, m! ?3 v. H4 N
    10 S8 G( j4 {4 k
    2
    : X' e0 {6 j$ M7 n' X( t2 m* E- o, B* B3) V0 w4 ~- m) Z4 a6 L! `
    4( B" p( S7 y* S( P9 ~. Q
    5
      e  C2 l+ p( P. {, {' r6
    ! k" o$ O" G) f( Q6 w7. ?7 E- ]2 A: I" L+ A
    8
    * h& r5 q' u6 ?  Q7 \1 x9
    - Z( d. @7 W4 @6 |* f10! T7 _3 d5 H, j: F0 m
    11# G0 ]# b2 S. a% H0 g
    12
    # ?! k/ [8 F$ C, `7 C3 S% z13
    7 c1 c; t$ e# E# @. ~0 T( y145 {; t  H" k; x( y" H. g
    15
    % S! s7 ^: ]7 r: P% e, s0 G16* ?/ j( t% p. t; G, ^
    17# r6 q: Y( G6 Y  C( t
    187 }! S) Z! m3 R' V
    19
    5 K3 d' q/ h5 k! j7 |' U20
    4 w8 ~) \+ l6 m( t217 `& f& S" t) F
    22
    7 z4 L" @* |6 H! @8.5 练习! ~/ X& _$ ^0 @9 m2 K+ H9 y5 ~
    Ex1:房屋信息数据集5 f5 f& k! b, e7 t3 u
    现有一份房屋信息数据集如下:( l7 R" T0 A  \. Q* o/ o
    6 n  K) n6 I  {) l+ C# K7 ]: w
    df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])" n2 s- t, O( c! W6 s3 L0 R; |
    df.head(3)  X2 }/ |* M+ Z2 M
    Out[115]:
    ; q: E. J" G& b3 q) t: |$ C- v& B      floor    year    area price
    ) {8 p6 o( M# p4 I$ l- H% Q0   高层(共6层)  1986年建  58.23㎡  155万
    * q) z$ j- ?, z3 G1  中层(共20层)  2020年建     88㎡  155万5 O: z6 [) f' o2 j4 N7 J
    2  低层(共28层)  2010年建  89.33㎡  365万
      K; h! B5 h9 F% W! o17 x4 J* n& H  b' v
    2
    6 `) a) m3 Z% E7 P7 m3
      J1 ]/ G+ y( D; I42 o, b0 P' P. z: D$ c: [& G! ]
    5
    ; ^# D* t  j; R+ V1 J. c6) i3 v- K! O7 s; q# W5 {. Y: u3 C7 K
    7
    % X( }! n' g3 j  o将year列改为整数年份存储。! C1 R8 _; D0 J7 j4 B: B+ V- [
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    ' S! b" l/ N8 q9 \+ a  r- ^: j计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数7 Y" X8 f! A3 t2 o9 O- K0 D
    将year列改为整数年份存储。; d. V  d/ c6 V7 |( p: @
    """
    2 a% S4 {5 N! F( P) b/ R: h整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。- P& U7 K5 u5 G1 N# I
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,6 H% W, A2 d. l1 R5 w5 Q& h
    转成int后,序列还有缺失值所以,还是变成了object。' k& O/ K( k! ?, l
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。$ g- T5 g5 b5 K
    """
    & Z5 Y% s2 _  ]- s' t* @df = df.convert_dtypes()
    2 ?" W. Z3 H: s1 Adf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    $ @3 _8 u5 P3 Sdf.loc[df.year.notna()]['year'].head()
    ( I# E. ~, A5 g+ T1 B" W
    & v# {8 d0 g, t' V% r. c0        1986
    6 F0 s. {# [' q: q! z2 ]9 k1        2020: K5 I5 [2 a" z2 M
    2        2010
    6 S7 D+ o. J  }/ ?3 s! }" w6 y3        2014
    + r& V8 J: l% u4        2015
    0 I0 x# f7 B5 Z1 j1 B$ xName: year, Length: 12850, dtype: Int643 L. @, a( G0 m- Q% Q+ U) y9 x

    0 V- {! a/ n3 s# O& R1 }1" L: {# e- S1 @& ^- k
    2' [% ~* t1 @: S0 S" v1 r. |
    3" r' D. t! F5 p/ ]2 [
    4
    ) E; a( R4 a% |  r* _! H5
    ' X- P& e- v% U6
    ; N+ r' }9 `0 f; n' M  S7, L& h& r# q7 {
    8: S; {3 f- ], c, z8 I; H
    9
    . @3 O" A0 z5 K4 U, E103 O: r# J( r9 y# }  V* @
    11
    9 D; G3 P/ \4 q( x- v" G12
    $ l0 r& ~. }2 ]+ }* _5 q6 E13  D/ r, }( F) u* c: Z% P: t& z
    143 ~/ ?9 o2 `7 S6 o8 Q% {' |4 ~; w
    15
    ! D6 r' a  J3 Z* q& J9 z16
    9 }1 w3 t) k9 @. Z3 P参考答案:
    $ ]. s( `8 u  |" r& W6 q: o3 @& k/ B4 `) n0 [. e6 r! y4 J
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么  k* J5 s3 J8 ?0 G5 h9 ~

    0 f; m* _3 M5 d4 `df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') 5 \+ C$ X! g' \) Y0 y. q% d5 K8 a
    df.loc[df.year.notna()]['year']
    # E% }! u: r( C6 O4 E1/ r1 Z: l0 e2 T( R0 T
    27 v! X( n- |& }0 ^  r6 a
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。1 M) N5 z8 b! M8 o; Q
    pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'. O: s2 {- `" u% j" |* ^# _( f- ^# b
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次9 [0 Q* q6 t" N" m( \/ b
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型1 u+ w, ^8 `' O* ?7 v2 D2 `+ e
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              1 D( A! c; T; m' k7 p' B9 g7 J% y. a
    df=df[['Level','Highest','year','area','price']]: n( c1 t( @& @& f' Y  J4 O. o
    df.head()
    , O' B" F3 H4 M: a* s7 }: n. [/ v' H" x! Y6 t1 s+ A( j. B
       Level  Highest        year        area        price
    ' Z2 l' e0 n, ]  L4 R0        高层                6                1986        58.23㎡        155万
    ' O6 d7 K$ I( i) E) [9 U- s1        中层                20                2020        88㎡        155万3 j' c) g" n0 c# q# H; j. g
    2        低层                28                2010        89.33㎡        365万
    % D4 u6 G7 Q0 P6 _3        低层                20                2014        82㎡        308万) e- f4 I3 @, V7 d& j( E
    4        高层                1                2015        98㎡        117万
      v% H1 S+ [0 ^( t! E& s! [- I1
    + i. Z* k' l3 z3 G; e* ]* ]2
    1 n  {) d( ^! e6 `4 m3
    ; g+ x% p' H( n3 h4
    & k0 P0 h# i% M6 B0 k" x5
    * n5 R; X# ~/ |6 y2 ~! S  P6
    6 q3 `/ E0 A, M4 @  q* n7
    7 b, r: }# u2 L' A7 |( j) I) K8, E8 B6 j) `. q+ F  t6 ^5 I& d
    9
    & p4 a  a* f! d' F1 y. e- f8 }" v10. V' Q+ M% {' f! _1 \
    11
    ; `. i/ T' y8 `12
    ! F0 g) [  M" J/ ^- ~* |- y2 U13
      s3 j0 u1 D; H; z9 |# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了' C3 K4 x2 U) i7 X2 U) J
    pat = '(\w层)(共(\d+)层)') s% ]& ~* T$ X- N
    new_cols = df.floor.str.extract(pat).rename(( F& G5 M5 r- p$ v- {
                        columns={0:'Level', 1:'Highest'})
    . O6 Q- q$ }/ ~& R& y
    " l( s8 L3 S9 t& N3 i8 ydf = pd.concat([df.drop(columns=['floor']), new_cols], 1)1 G7 @7 H2 u. S3 Y9 q
    df.head(3): o3 \; R3 ]( ?

    1 J, Y* f( ^" S$ cOut[163]:
    . V: Z2 ^& b# b- j- w7 J   year    area price    Level Highest0 W+ ~2 x. d7 V1 P
    0  1986  58.23㎡  155万    高层       6
    - b3 a7 L! D7 t* ^! U2 @1  2020     88㎡  155万    中层      202 {' Y1 b  S6 |7 A4 n
    2  2010  89.33㎡  365万    低层      28# }4 Q" r" v) E3 a
    1
    3 m: f( l2 I, ], D" Z2 G9 N6 }4 e2; N4 I3 y/ g9 F3 ]
    37 D$ T* ^9 W0 w  y1 Q' E; S2 g& f
    4
    9 l# Y" l6 i3 v9 J/ u52 z6 P3 W9 u) N. M! E: D$ S3 k
    6
    / F* [9 }% }& f- @* ^& N7* x, @* e( h$ \4 Q. b: E
    8
    0 w) W  x$ a# ]. K9  V5 O+ G4 }* G$ T. k: v1 N
    10# P$ v  D1 s, r* m2 A  Y. ?- ~
    11+ a7 ^" M/ ?2 K& R( P7 O
    124 R7 r% D# ]" F
    13
    ( @9 |8 G( S1 k- z( L2 M2 G计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    , Q4 i: O, K, z/ o( I( C"""
    " h. H" H* y1 l. ?/ n1 M# q1 R( ]str.findall返回的结果都是列表,只能用apply取值去掉列表形式
    3 @  c' ^9 p5 I! [( j参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    7 F( `5 O. @; \由于area和price都没有缺失值,所以可以直接转类型
    6 U7 u1 m6 ?, Y9 o. R+ t1 d"""$ ?* Q; t; U1 V& A8 f
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
    : K# @) P7 C! L- R; Z8 tdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')' i/ ~+ l' S  J# L6 W5 t9 A( q1 O1 S( a
    df.eval('avg_price=10000*new_price/new_area',inplace=True)
      I, z) f, s+ Z5 s) a: ]% k/ a# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    " j& y" b2 {9 [* O, h# 最后数字+元/平米写法更简单
    + @/ N" Y! e0 odf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
    ; B  a" S/ H! t* }' |) L  i9 [del df['new_area'],df['new_price']
    $ q) I! ]6 {5 p) q4 ddf.head()
    , x  d2 Z  h* S- o+ u* x7 f1 R* }3 K5 x3 E- x
       Level        Highest        year        area        price        avg_price
    8 B; n# L2 ^4 K. N% ^8 R0        高层                        6        1986        58.23㎡        155万        26618元/平米0 _. c3 I9 b& z2 G1 q$ j& w
    1        中层                        20        2020        88㎡        155万        17613元/平米; c7 Q$ T- c1 }) T( t9 X0 ^
    2        低层                        28        2010        89.33㎡        365万        40859元/平米7 A4 {5 L! n8 M  ?
    3        低层                        20        2014        82㎡        308万        37560元/平米. S% O3 z* b/ p: X" G9 R! d
    4        高层                        1        2015        98㎡        117万        11938元/平米# k3 b5 B+ O, t' c: J3 K  d( _* h" B

    ) M* A% p) a; T) k) J* \1
    ( H9 ^- l! N: k4 {% b2& u9 d+ R/ d9 j/ r: g! V6 N& E
    3* G5 \& u1 d# o% A% F
    40 J1 [9 U; m4 a
    5
    % a8 K0 Y" ]: u% L6 i: l6- l3 }  l/ i2 C- x9 S8 {, @; H
    7
    0 K* ?! O2 s9 k4 h7 s9 l* p8
    6 X+ N1 E7 ~* ~* z3 t/ T9
    * X4 l) I' o+ c3 U/ [9 m9 e10
    . |# c! d# h- @7 Z7 [( t; u7 ?2 I11
    , D/ f+ k1 h2 l3 w. ]# c1 r' F12" w7 ~' W* b6 d# k
    13' Z, t* ~5 r, N5 a! P/ M* ?
    14; c" G7 u4 W0 N9 f& [% B
    15) V$ i/ i; s. }
    16
    + S" W% N/ |$ b, U0 F& `173 j1 P. R) h2 F6 Y: p
    183 G" |8 K( {5 P+ ^- C3 v/ g  J1 E
    19' J/ B3 @6 g0 \
    20
    : C% n, t8 j) P8 E" o# 参考答案# `7 L( e& r2 p$ [$ Z; ^
    s_area = pd.to_numeric(df.area.str[:-1])2 u( @- I+ Y" N  p8 P
    s_price = pd.to_numeric(df.price.str[:-1]): H: _% }8 c4 b. @: J" D9 T" C
    df['avg_price'] = ((s_price/s_area)*10000).astype(
    $ B  U3 W/ i% R9 H                    'int').astype('string') + '元/平米'' s( ~9 \4 l) H5 N  V

    ' o3 h. ^& H6 D2 B# d0 j; X4 edf.head(3)4 w" h# Q  _) c
    Out[167]:   [7 \& {4 |; u6 G/ A2 x" V$ I2 M
       year    area   price   Level Highest  avg_price
    % o) M" f* h) j. S2 j) \0  1986  58.23㎡  155万    高层     6          26618元/平米
      Y$ G8 J' H2 z1  2020     88㎡  155万    中层     20          17613元/平米& ?; h5 @: b1 v, ?
    2  2010  89.33㎡  365万    低层     28          40859元/平米# t6 m. Z0 x1 T7 D0 q
    1# B4 |0 X; J+ {) D
    2- `  C6 i: K/ }2 ^2 w, e& X. v
    3
    / y6 ~$ m  `4 \) o49 N- y9 n* o* Z& s/ M
    5) m8 \" ^9 p& z; _* n% a
    6
    / h1 k' k9 T! ^, Q0 t3 O9 _7
    7 w1 k7 U$ K- S# x. j8
    ) V' s& I* O/ Y95 t7 C6 |/ D+ \' ?* w
    10. E, [$ R  p* }4 T
    11
    % W& s8 _+ |; y$ @- Z( ~128 S8 k  [8 }# o1 g7 R7 ^) S
    Ex2:《权力的游戏》剧本数据集
    1 k( B- b) I+ w+ @现有一份权力的游戏剧本数据集如下:! ]1 u' W: Y" I5 y

    0 i( K) y6 o' w! v% Q* Zdf = pd.read_csv('../data/script.csv')$ Q( V0 E+ w% n& Z2 a1 k
    df.head(3)
    , q7 p  i0 ^0 F# _0 g2 N
    , h) W: g0 p4 Y" s9 O; q! i4 ^Out[115]: 4 E" X; J# F( M, ?+ h' e
    Out[117]: 4 ^( `6 m4 j) w' A: \9 K' g
      Release Date    Season   Episode      Episode Title          Name                                           Sentence8 }1 P' n: Y& H+ b  i0 f
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    4 i# j4 T! X: H0 @: v; Z4 n6 w1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this.../ o) C! r3 X6 l8 T
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    ' F* W% b; _! P1 F! ]; a- g+ |1 w1
    % d( a8 o5 M! b  E6 F" @1 v2
    . D1 o6 u4 a! O; B% @# d# M3
    # ~; J" j! }5 h0 h4
    6 U& v& n8 b4 D3 H+ b* G7 U2 x( D  X5
    ) k9 o* F! C* {" r: n9 p% L63 U7 T4 @+ ?, V& J4 X8 A
    7; l5 `# Q# B" w3 C. l
    87 P; D  i( A6 F: p% f4 K1 \
    97 V+ C- Q1 h) N) e1 f+ r* n  ~
    计算每一个Episode的台词条数。8 t9 v+ s$ \+ z+ g6 d& p8 X
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。5 Q: C9 ?/ N- ^1 i) F
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    2 v- o) b6 j( ^8 b( G计算每一个Episode的台词条数。
    ! X% T, o5 C4 L/ T3 N: fdf.columns =df.columns.str.strip() #  列名中有空格
    # u% S4 z. A( \4 b% t+ hdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head(), w3 c+ V7 {1 b- N
    0 u" n9 |" H9 q  X
    season    Episode  7 Q  `( b& d8 m: E$ t! x( A& N
    Season 7  Episode 5    505* y5 q! Q% l1 S
    Season 3  Episode 2    480+ \4 v2 i1 ?8 H; D. J7 R
    Season 4  Episode 1    475. X1 S2 A7 E% K' H+ n  _# b  E8 y
    Season 3  Episode 5    440
    5 L! X6 y  s5 m) H1 {& ]8 X/ G; ], `Season 2  Episode 2    432
    , X0 L! m: e$ t1 O: m; z19 X5 j* [6 p: P& d& t' T
    2
    ( r1 }8 h, Y9 Q6 S3# ^3 `5 L1 g$ H$ ?
    42 N$ v+ D9 x8 T% Z
    5
    5 w' E' W( v: n" \* Q6
    1 J0 e  v  v/ R* S0 {0 g& g9 D& b' b7
    8 ^% b1 I. L+ g6 c85 {2 D$ S3 x' x9 j) z7 g
    9* |9 u; z* I. L! y7 e, P
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    # a6 @" m1 w8 N$ @8 C& G6 i# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数: a9 j1 r; |$ k4 J2 A0 }# \
    df['len_words']=df['Sentence'].str.count(r' ')+1
    ' c0 `2 B% }4 C% v" _5 Jdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()9 g8 W4 }" w7 F3 t* h7 S
    " G+ N" |# E5 k2 N
    Name5 t9 V2 L+ t8 n& a" d! w
    male singer          109.000000
    ( v$ Q# z2 Z! Hslave owner           77.0000000 f9 e& f  N; Z
    manderly              62.0000004 Y- |! S; @+ H1 i5 ~, L. K( _1 s
    lollys stokeworth     62.000000
    $ D- D& t1 o8 g2 X2 jdothraki matron       56.666667
    # f9 K' `2 S0 XName: len_words, dtype: float64
    9 y8 F8 G; j4 A6 F: U1
    / s# n# N3 \3 L- H24 k4 D5 Q) i, Y) t
    3
    4 `; Z8 D% `, t) h5 o44 w6 h: ^5 b; q# N* F0 ~
    5. O3 @, S5 R% x3 Q9 V
    6
    5 E) j* B) }2 X* K8 ^7
    6 w& s+ ]- Y& v, x  H1 G& o88 T$ G0 s- ]( r& g
    9% n) \& f: d+ W, {( {- ?" D
    10  A9 w% u8 `0 w1 I
    115 r7 |% S- x4 @8 {) q
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。) A# B3 s2 ^& G- f* R, p
    df['Sentence'].str.count(r'\?') #  计算每人提问数
    " _7 l& Q* J4 O3 q* cls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
    ( w0 p& ?, C6 ddel ls[23911] # 末行删去! x' Y0 O* r9 [% n1 ?: ~
    df['len_questions']=ls
    ( z- o2 Q; Z. ~- a. Mdf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()6 U0 b3 W# L6 O- a" b

    9 [$ J1 o% S8 G1 R7 G0 {* @! ?Name
    7 n  e- W: ~/ G# t! utyrion lannister    527! y" h9 ?6 j. J7 d$ X4 Z
    jon snow            374& J; l+ W2 I) l0 e2 r$ F
    jaime lannister     283& L; g) b$ \3 ?
    arya stark          2650 S6 l' z/ T1 w  k
    cersei lannister    2468 w9 i9 ~" |! g
    Name: len_questions, dtype: int64
    ' h0 R' Y* b/ J9 [2 T0 j
    6 M! ~8 }" @* q% `# 参考答案- S% j- P  C0 ?5 W; o. i
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    7 i- x' t0 Z* c* `, ^s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head(): d- l: Q7 Q/ ?. d' a0 K. \! Y( q

    : a) z/ o! a; a1
    + j" L. N, A* m8 r) B2+ L  \" W1 C  F  X8 F9 G+ T
    3/ w0 H: Z7 j" z; V. U! F
    4
    & p: g& e# W2 ^5+ V& ]  h; ]( J% Y9 I& J+ B
    6
    , W' [) g. ^4 S/ ^2 }5 L7
    $ J7 T9 G. N; k( [- Z  m. d$ D' F6 {8( ?  e5 t1 B$ _
    90 h* B+ B. b- N6 Z, Y: u/ y
    10
    8 p# s& b7 I/ S; ~! F11  B! ^  u% H* {6 N% W* c$ k
    12
    ' k) V* u4 \0 R9 t6 W# M13
    3 J2 |3 h0 o6 D5 n& c14( K4 a# C6 w/ Y& M, p
    15
    2 m& {. [% @, |) {7 q16
    : ~; s  t* e  i) I3 f: h17" }. U$ o" e: N. Q; u0 @
    第九章 分类数据$ R8 ]% _) ?* Z# X9 w& \5 p
    import numpy as np" }  k+ V) A: U2 k. L" t: _! L/ f
    import pandas as pd
    - G% x. E* y; C: P6 s1
    & h4 |; d) Q3 |% ?, a: o2
    6 E  S: ?7 p; t' p7 Y! B' G+ `; g) @9.1 cat对象
    ; B0 V: w3 D& d3 [/ P9.1.1 cat对象的属性- Z. R5 }% n+ D
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。2 D8 ]( i2 G' }+ S
      |( m4 j1 }8 I# X
    df = pd.read_csv('data/learn_pandas.csv',) E! f1 h: b5 Q+ Z4 r
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    : m/ L# z- j$ ss = df.Grade.astype('category')
    1 Z: U$ C) w* `# S) q" g: H& r# E* E/ G3 l( P
    s.head()
    * b- a; R  L% p$ ^+ g$ ]4 r+ tOut[5]:
    7 o! b' Z3 }- D' s, M$ f& F0     Freshman9 O# Q+ b" a5 T/ n; O. y( b- `
    1     Freshman5 Q1 N% t9 _: L; ]7 }
    2       Senior
    , I, y4 O/ f; X, O, @3 r3    Sophomore
    7 i0 M, _' r$ A9 W4    Sophomore
    + |( y4 w' N  a* `4 zName: Grade, dtype: category  {: }6 R( B# z2 e! s
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']4 f0 A/ \3 x2 o. _  g# s& h4 n* t/ Z
    1+ J7 R" {. I5 l# o
    2
    4 t& z/ ^# x5 v& ~/ E- [3 L* |3) J7 I0 J- ?9 C- |9 \
    4
    7 E# C" J( q. t3 M0 V% m5: q- u9 g( n! L5 T% R- H
    6- A/ Y% x- _9 Y/ F& c3 R3 r
    7
    ( @  O4 t. u4 y) M' o2 N82 D  r$ ^$ \- m8 i8 P; z
    9- J5 Y0 E$ m8 L9 h' H( B, o. q
    10' K7 ]! {5 Y. x6 ~
    11# Y2 ]- I6 Q% f. _
    12
    & [+ i/ V: N0 n2 p  {8 Z13
    $ i5 H$ {& k2 W, o- x  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    & X0 h( [2 }( `+ U/ j
    & n+ S  B) E: g: i( Es.cat0 |# d/ s. {' [1 N  w& K1 P/ E
    Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>$ }5 A" X! a4 V
    1
      t; B9 |: _' n3 C, m, v22 G$ s1 {8 E# p
    cat的属性:# V% E, Z- [4 s) w

    4 z- Z9 j4 w: m8 |* Jcat.categories:查看类别的本身,它以Index类型存储
    % w8 O3 w# _, \+ Ucat.ordered:类别是否有序) s& j. Z7 j' n- Q5 ~& J' g
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序% ~5 T2 s/ G& l" q- u2 C# D
    s.cat.categories
    ) e& G- e; W4 Y6 [# L' nOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')3 A. a' o" |7 @# x+ O$ X6 X
    % A7 L4 S9 |7 e7 z3 G
    s.cat.ordered5 a3 g7 Y, `: g. ^* y+ {: B
    Out[8]: False
    ) X$ Q, }+ t* ]* a# u4 P1 Z8 w. B2 N
    s.cat.codes.head()7 Z/ d' b- a  C# k7 s
    Out[9]:
    2 q- P, ~0 j3 Z0    0
    ( Z" k  J# i5 X6 }0 m' U1    0( \' R, w: P$ M- P
    2    25 U6 j5 B; u" b- X
    3    3
    ' t. v' [: |, f; l5 Z# J4    3
    " g* q: Y- _* A$ }% ?dtype: int8% K4 ]$ ^* v- k) Y$ c3 N( m7 E) H
    1
    8 k0 s; _" p7 _2
    + `2 E; ~4 i0 s& ?  D; x& k* H33 J1 t" J% F$ I" n8 m" ]& }! I
    46 j& G6 O% i. a8 b. ~
    5% W2 I0 ?+ s" }+ O) G- c: n
    6+ Z% ]. V) w4 e0 w
    7$ p, C' s$ P: x3 y
    8
    ( r$ k/ d' Q# Z+ @, ~: G9
    & L: r0 C, l' n8 ]7 J' F- H10
    9 J4 F0 C! _4 e" i11
    5 F! l# H" e3 x8 a, N2 S; A5 T12+ L8 H3 l# g; d0 T% q/ E
    13- U7 Q/ z# F  ?7 ]+ U% |
    14! n* g9 z+ o1 ]
    9.1.2 类别的增加、删除和修改
    2 }* g# D2 [9 Y, G1 |+ ^% Q8 u: x  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?% J7 |& u( |8 B( w

    , v- R% J, g/ O3 A【NOTE】类别不得直接修改
    . |8 ~; D; w/ s' r在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。# I# `: m: m0 A. G$ f* H& a+ a1 w6 g
    & e  k; W" o8 x6 P( U9 L
    add_categories:增加类别4 g' T, }- u( p& }* T5 ?
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别! ?4 }  `/ F' A  _% A% x+ Q, h
    s.cat.categories  P# \. F1 T3 B
    ; i$ A9 e' a# S. b3 c9 B( A' l1 }
    Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    : [$ Z+ F2 w3 a1
    ! g9 _/ y$ V9 t+ K7 s' O' i: _2
    ! u) s( w0 N" @) W, [6 Z3
    % D: y& i; C7 U* l' s" W# n4& Y$ I3 T5 \* ^5 S6 B
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    : f5 |! G# a; m1 bs = s.cat.remove_categories('Freshman')
    & i/ n, l5 _$ w: W6 S# n/ Y" g& b8 |$ f
    s.cat.categories4 w- |# H; h% O; s% a8 T: l8 T
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    + ?+ P: Z. D4 O- s2 [: ^5 o6 Z7 X& i0 z6 k  p1 m, b
    s.head()
      K5 R$ p5 _3 w7 ]% D- |4 r( L# q+ COut[14]: " H4 _& }' g' U1 x1 c
    0          NaN
    : f! L: `- A1 x: n1          NaN) d# o8 L& m) W' F  Z
    2       Senior
    % Q, j; o+ `8 ]8 e6 q3    Sophomore
    + q7 b% h+ K( u: [; F4    Sophomore* f+ s2 E; ?. I  _! E2 x, v
    Name: Grade, dtype: category! k4 q, C! R* Z' ?! s7 A) x- N2 y3 B
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    & e3 X0 ]( M7 P, x5 d1, [3 W8 N9 s" S" F  |- b7 b
    2
    7 M3 `$ S2 h0 y: Z3
    ( r. A( C  l$ z0 L% L( ?4
    ( }: v3 a3 L& G50 y9 D, M( D% k3 m* h3 F# C
    6
    % D: J) T& E/ S' U3 p. t& j7) q2 W/ u2 @$ V/ e5 ]  H* \
    8
    2 H5 X0 _/ R( f$ S9
    8 n. I8 |$ `# u8 K2 ?! o% b10
    + y: T) b' V2 K/ A# b* J11
    " {& [7 O9 c) e, g+ P12
    * @0 i3 r. j, n4 j13" |/ g  i2 [1 _5 ?+ y( j. M. d: g
    14
    6 }+ a( b$ Y6 V" u' ?  T' z- uset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。" Z  [- C4 I$ q  I
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
    5 R# S$ R) P3 [( U1 P$ os.cat.categories8 R8 G& D: |; w
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')" V( X3 [0 I8 l
    + N6 z! ]) m; z& D5 @" S
    s.head()" y2 H0 W# G- A" Z4 M
    Out[17]:
    ) {2 h2 i; O/ ]% d( [  q0          NaN
    ' b; Z9 f5 h( R2 Z+ c1          NaN& h  ~) b+ ~$ _: ^
    2          NaN
    9 `- i9 d! `4 U2 T4 z/ I3    Sophomore
    $ u) U$ r. u- T: |# p! O" |4    Sophomore; ^8 P& h0 O  E( \* p
    Name: Grade, dtype: category
    + d0 O/ @! a% w, Z& `. x* JCategories (2, object): ['Sophomore', 'PhD']$ [. O6 J* j, D3 }% `$ V) z& {
    1) K2 h8 k/ O: h
    2
    . N3 j2 P1 N7 \+ n' O. _- k9 O+ e* `39 p: n0 Y6 o" G8 c7 z. \+ }) s( _
    4
    % E6 r- N( x# L; ~! \5
    ' }. u; A$ F; |* B, g# s6. b; L7 P9 t$ y( [1 P
    71 b4 ^9 N' [9 i- @
    8% ?* h+ ^# ]6 H+ p( z% y
    96 h1 J7 f! a$ H. K1 K; s  }$ [6 x
    10# k! d" z* l' i' I5 C
    11! a5 y2 _4 u2 K9 d9 x
    12" D1 _) G. ?# p! R: ]/ N1 u: {
    138 K- Z0 N2 s; y% i! {  N: O8 T: ?
    remove_unused_categories:删除未出现在序列中的类别
    6 F- }/ @% c  C3 q. W7 d9 js = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    5 @- f0 y% D9 `" N% S, {s.cat.categories
    ! i) ~4 s$ X8 r) C/ m/ p% F4 r, }& {# E3 e# r
    Index(['Sophomore'], dtype='object')
    6 _$ V4 O. e: b3 o# @) j! k. K1
    ! L, X) l% u, H: u9 }' l( |& y+ D7 T- ~2  ?8 V2 J) x) Y, G) F- s  N
    3: {/ V2 p, c/ M7 @
    4& y; ?  S( g$ \# R* l% O. F
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    ( D' P& B: g4 Q% _5 C/ y# ?* ys = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    $ s( _3 s3 F" a" K- y/ e, ts.head()0 F# ]1 y, a8 l6 u$ i2 X
    " k* i5 U# V" {
    0        NaN0 G- j7 `: Z+ o4 V6 F7 u9 Q
    1        NaN' }/ t6 \$ p. ^- {! I0 B5 n1 q
    2        NaN( m! c# |1 \" I$ Q
    3    本科二年级学生
    3 m# Z8 U$ w& E  d3 e4    本科二年级学生: o; b% B! \- w7 q% t/ h% N
    Name: Grade, dtype: category
    : b/ |$ [3 Q% o4 XCategories (1, object): ['本科二年级学生']0 Q1 H" E. _& V7 e; _5 J2 j0 m. D
    1+ y. T5 a  r( o+ j) E% {+ c. G1 _
    28 m4 |1 e$ R; ?" \- t( N! |" i
    3
    3 I. q- H, R0 S2 M1 P' ^7 Q4
    ) x/ j% y/ Q+ c; ^9 A5! h8 h2 e, D, }0 g3 P$ v& @5 r1 w0 j, _) o
    6
    4 K$ f* b* v3 m' n8 ?& d2 ~7- l; ~) v( U# ~5 f; l
    8
    6 G2 V5 x: h( o9; [( h2 l% i1 [: D; V0 W
    10; @0 Y8 j: N  l: N
    9.2 有序分类2 v8 C' h% t6 M9 @. s3 `
    9.2.1 序的建立
    6 s% Z# X  ?6 q* ]  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:( }& \% ]& c6 E) E
      w! }1 k, d, m) ^
    s = df.Grade.astype('category')
    , S: }9 a- B& ^, `2 J' Os = s.cat.reorder_categories(['Freshman', 'Sophomore',* S  J- M5 m0 U3 m) o# l/ u
                                  'Junior', 'Senior'],ordered=True)
    # i& C1 n. f+ e( c: j4 G" Ps.head()
    . N4 d2 `' U4 z& {Out[24]: * y0 q+ n% r$ V
    0     Freshman
    7 b7 k' t5 o5 [! @# T1     Freshman
    4 Q7 x+ e) E) k5 h% K2       Senior/ D4 I4 S' K& S) K" j3 ~/ _# Y, G
    3    Sophomore2 D0 o' ~0 w# s
    4    Sophomore8 [& ^# ?# d  c' I9 n" D! X
    Name: Grade, dtype: category
    / k9 b' T' k9 K( E7 B! P- {1 ]7 yCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    7 J  o8 s  P9 k+ A- @( H5 a) V& h9 g: d$ ?$ L! t- ]
    s.cat.as_unordered().head()
    5 ^- x7 _2 |- b! S) z- cOut[25]: ; I; J6 g, Z! k4 C1 @! X# P
    0     Freshman
    5 J/ ]0 w" T! _2 j3 ]1     Freshman
    6 h! Y, U! R% I3 T( G5 W9 ^7 }% \2       Senior
    * b( i: g: G& _& H3    Sophomore/ q# t* ]3 J* @( v1 ^0 l- A
    4    Sophomore
    9 u5 I1 N0 U0 o6 K' f* [' {Name: Grade, dtype: category, }8 G+ g. o" q2 V5 O3 f0 A2 M
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    ; j' \; l6 U2 k) _5 B" F1 a# I7 |+ t. ^, K  V
    1
      B3 q) j( ]! R. |3 u9 i6 y# d2
    ! M* ~! ~- l8 c, z" F) ^3# P* q, |" O  N- H" {9 i
    4
    2 Y9 x( F+ z7 a0 y7 p5
    7 |2 p# {& t3 }67 l$ v, |: u9 Y5 K6 z
    7
    / a1 s1 o0 ?( g; p8
    , {5 g; _1 `, ]( m7 c/ N  v$ a9
    ) R( O; q  C. }( _+ @! _- ?10
    0 n/ C8 }6 G4 N$ j* r11  D6 N7 I2 N. S% m2 Z* D. m
    126 X# ^( [# Y  Q( {
    13
    0 Q$ e$ q) E5 ^  @1 q) i141 q) I' ~4 s/ ~/ R' Q1 }7 i
    15' r: F0 I: O3 ~5 |8 n9 H
    160 @" _, o$ y( d& S
    170 j$ a5 o; O! I7 ~6 W1 [
    18% ^0 T; g! b1 P. c1 M
    19% a; k- w% j' p; G$ T( S7 V
    20; d2 r4 W* s* v! I9 l8 ^
    215 L7 \) M4 s/ O9 `& }
    222 t' Y" Q- F2 x% |( h5 M6 x" d
      如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    2 ?6 i; }2 c  \! m4 J3 a4 f0 W3 O# S
    9.2.2 排序和比较! X, R) X% `0 c2 C6 Y4 ~
    在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    9 `+ C! j4 ]8 z; J+ S4 R. f
    $ |/ c) d: c; t5 C5 f# J* H. {  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    ( s1 s. L( }; _& ?
    % X" D, b3 |! J  `/ m- H% \# H* ?' xdf.Grade = df.Grade.astype('category')! J1 q4 q, N  c( i5 X
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    / w" d3 x' I: T% S1 L% L1 |df.sort_values('Grade').head() # 值排序
    ! \. H2 T- ~1 F; T% @% MOut[28]: - k3 N0 A4 b/ B4 z$ E
            Grade           Name  Gender  Height  Weight
    ' L' d0 M% W2 I5 d9 Y$ }0    Freshman   Gaopeng Yang  Female   158.9    46.0
    " m& a: u/ j6 {; ^4 V( `7 Q( ]105  Freshman      Qiang Shi  Female   164.5    52.03 _2 T  P" d, F. }3 I( @# _
    96   Freshman  Changmei Feng  Female   163.8    56.0
    . Z# H5 p: J0 \; _1 r* {88   Freshman   Xiaopeng Han  Female   164.1    53.09 ^+ V6 [3 V4 @3 M$ b$ x) z
    81   Freshman    Yanli Zhang  Female   165.1    52.0
      O5 {% O$ M& o0 T
      G/ G, G+ c9 H; z2 Fdf.set_index('Grade').sort_index().head() # 索引排序9 _) L: ?; ^, ^3 |
    Out[29]: / p6 Z9 ?; i) n+ n/ L0 D5 z8 ]; }
                       Name  Gender  Height  Weight- l. Y! [4 a2 ?3 R8 `3 t# ]
    Grade                                          
    8 T" z# T5 M3 q9 q' R! QFreshman   Gaopeng Yang  Female   158.9    46.0
    ; e  x; f1 _$ d  k8 LFreshman      Qiang Shi  Female   164.5    52.0
    . V1 U1 o9 Y4 x4 J7 A+ WFreshman  Changmei Feng  Female   163.8    56.0
    ; `, c2 M4 E1 A" x' E! }$ h( tFreshman   Xiaopeng Han  Female   164.1    53.03 o* b$ L  l3 I1 d5 Z1 [; \
    Freshman    Yanli Zhang  Female   165.1    52.0
    4 h( }% |+ h  j5 z$ R2 O, A, V/ u) I) |* Y
    1
    $ i- `. I) N4 C; `  ^28 c( |7 a  g0 v7 p0 |0 u
    3
    - F2 y: z: c: @5 G- U4& p* p- V$ z1 V  z4 `; d( n
    5  b2 u6 G" ^  z2 Y1 u! c) N0 g: ?, [
    6
    & l' o; l: j+ _/ c, W6 I7
    5 h  p8 {: z# B' B: y1 D8
    8 x# [% f3 Y  c/ Z8 L9 t2 r: C9
    * ~  h0 U! H$ ~! p& L+ U10) |2 @( V1 F; o2 t" D+ ^
    11& Q5 y0 z1 I2 Y- ?4 h0 o
    12
    1 N1 \2 P* S; {) P13
    4 m; Q& e1 y* t/ ]% c/ G5 S, I; D14; c, i! E* x0 ]* b
    15( G0 d9 z6 L6 G9 p
    16
    . N& W1 |$ u1 L& m) m; _17
    ! u% K" S( J, Y; i% K: T4 e3 B18
    & d0 x7 n) m2 o19
      b. ^7 l; e. m# z) P20
    ; @$ y- p" r* z/ Q7 a  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:6 r; s3 G- Y2 H; t, K3 R; j

    1 F) t1 M" u! F5 Z0 ]/ e  Y==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    & {& O/ D$ K. T. I>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    ' j" ?$ [! `4 t7 g3 Kres1 = df.Grade == 'Sophomore'. v) r' ~( e. b8 T( q' g

    / q% }2 ?. J; l# F' v  L# lres1.head()0 c2 h7 E. @+ L' i& A6 ^
    Out[31]: - S! x& j8 Y3 R" R
    0    False
    ' k! Z+ _, K' W0 C& N1    False
    1 A9 O  P6 g$ c' A% t. o; Z8 o2    False
    + ^0 i2 P9 I: F* b! ?* N3     True
    / n% c$ v5 ?/ r) \2 w7 E& Z4     True
    % g. l5 ^4 m' fName: Grade, dtype: bool' M- F/ a/ T. F, I; n$ v% }

    8 J: ]' Z: g) T( mres2 = df.Grade == ['PhD']*df.shape[0]
    " l8 T" b7 O( Q1 r( r1 e: N
    / n9 l, N0 \. x$ ^- C2 [$ C* Pres2.head()- k) J+ n7 N% c" T. B
    Out[33]: ; P! L+ e( ~6 K  w2 @
    0    False
    + \0 |2 p* o- p1    False
    + p* m# e. `& U" S6 i* D& t( I2    False
    # K- Z# O  f+ E1 l% V3    False, M# E, Q' ?  s9 K
    4    False
    " W( J. n- Z6 o4 MName: Grade, dtype: bool+ H6 _  l) W6 o( P& I! U$ u
    ' W; n, d2 |7 S' z( W, N7 \
    res3 = df.Grade <= 'Sophomore'
    5 b' E. o( ^8 N. o" w% B
    : `$ _& P! A; c+ v' }5 D7 K' ures3.head()) L! L" l$ Z, p- V# J. `
    Out[35]:
    8 y3 z% [9 o9 w3 \: b0     True
    5 l! W+ R+ J4 H1 Y8 l, q6 _1     True8 F; }3 e& x; ~/ w4 _: x
    2    False
      M* j& ~( \1 i: u3 |; P3     True# X  G+ ^8 L7 \1 e+ i: a
    4     True
    3 c, h6 S' g' r; Z0 [$ ^- ?/ sName: Grade, dtype: bool. x+ l# S7 \- u  e5 g+ [/ x
    0 L' Y: m+ a$ l9 Y7 y
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。3 i0 Y) v7 e6 ~) T$ o: m6 j
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    6 e& ~6 `& B8 {: z5 w" B: V7 A
    6 j6 @. j' _) Y! z& b- Rres4.head()) ^8 z" b3 L. X
    Out[37]:
    2 A3 a; q/ t1 c5 e! h. _0     True
    ' q  C( t, K' g. ^8 c; ~1     True, Y' L, ]! p3 u; F: \+ v
    2    False  Z  U( `* T1 z# H& G* [
    3     True
    ; z; m% I3 L7 B7 e9 s8 O# Z4     True  o) v" r' f- x. y- {- p+ P
    Name: Grade, dtype: bool
      h- q) p6 Q! K+ ]1 a" s
    ' v; H' G$ f/ N' r% y7 m" ~; j; j9 P: e1
      |9 N% A% S5 H' X3 C* U- W2$ f: C2 e' m! _& V. u# Q  }
    3
    . T9 G+ u" o* m: Z- b4+ l9 a  R( P6 O9 [6 t2 V/ H
    5
    ( }/ V, A2 Q$ ]6! k) `$ L% {6 _: G1 U3 B
    7+ `- c( v& w6 f$ d+ O) s& P: f
    8
    : o7 @* G& R- |  e- p9% T, B- B$ F# y5 Z( N
    10! m* k. e' ^3 W2 T6 c' m( e
    112 |" q! r" z% f; g
    12
    * r+ w: W6 m7 o0 H6 S133 p1 g' g8 S; r! D
    14
    6 L0 N8 f) d7 j( }$ y, p15
    ( P  ]' y, Q- Y0 |16
    # z! l+ M0 A- g, H" X8 ~17
    8 \; K8 t- e* g- k18
    : A% H# k4 u: S0 L% U& H4 S* b191 V: }0 G/ h) J  T! ?  J3 l
    200 u; `7 O# P: ~" L+ Y$ C3 x# u
    21
    6 f1 a) w* V$ O22( o5 m. I: T/ I0 _# c
    23
    % R- ^+ A: g4 M& f, |0 Q24  Q1 `( t* j: g
    25: m" V2 K% h5 O$ I& ]- ~
    264 a- m9 ^" {* A  Q- {# q
    27
    * E% v1 [6 P4 Y+ e  \# ?8 g, E9 _283 f1 M% [/ A" q, b4 q
    29  b4 v/ L6 i; b2 l" ?" U
    30
    , W9 @4 [$ V$ E$ Z31* U5 _7 w- C- T2 \. q. E! |  |* N
    32
    - G( G9 L2 G! [: g33
    / i3 H7 h8 m$ h& d1 I34& v" n9 T8 v$ f
    353 p8 S/ K) u" w: T
    367 m0 _3 T( V! [# M# o) F9 M& n' M
    37
    ; j  l% H% y0 U* f382 T7 Y: f9 A2 v, s' l1 C# Y$ X+ M$ u
    39  `) s6 a9 @' [
    404 e: Y( l; L: J( p  H
    41' o( N# Y+ X" H$ |3 W0 h# S
    42
    4 N7 s/ N+ m$ o) ?438 P8 y( o+ }" U8 Y9 R+ d  Y
    44
    - z9 x, I, F! Z- v6 f$ K) a4 x9.3 区间类别
    7 k% d5 s9 [( b- `# q9.3.1 利用cut和qcut进行区间构造
    & [; J  {% t, U  J  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。# V, K$ }9 U# p/ o) O7 O

    2 P7 B8 S+ a; j* acut函数常用参数有:8 R0 B8 j6 b1 s' [2 D1 z- v& g: w/ d
    bins:最重要的参数。
    / ]9 L2 a; b( P& B如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)4 i( Q+ ]' j& k, O# D! X
    也可以传入列表,表示按指定区间分割点分割。
    0 a1 A! J. y- \% \, X  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。4 X9 x0 i$ s, R! ~7 q8 I
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    0 \6 s9 _1 A, [( U
    4 J, K' W& W% Z5 {s = pd.Series([1,2])5 U0 T1 E/ {5 C. W. w+ J
    # bin传入整数
    9 d, H' ]2 S  K1 {
    3 a7 h9 I# W& W) }8 \6 I/ u% Ipd.cut(s, bins=2). i1 Q0 I0 G6 S' o  r
    Out[39]: % N7 O( r. P/ i# d
    0    (0.999, 1.5]
    6 S1 J& N( d9 e1      (1.5, 2.0]* S/ k. J! J8 V# G0 V
    dtype: category
    3 q2 ~( ^! W0 R, cCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    7 g+ J8 h* M% R! Z  \4 t4 _( g# Q. d/ t# j1 Z
    pd.cut(s, bins=2, right=False)
    8 n! \8 L* O8 h; H! Y- f! G( JOut[40]:
    ' ]; p. k- {4 X  G0      [1.0, 1.5)% n/ W0 G2 P* T
    1    [1.5, 2.001)
    7 Y9 [% @( B& V3 z/ f# {dtype: category
    $ a: r  y8 e9 y  ]' _Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]& I) q% d, E1 e1 M. P. J5 i  o
    9 @) \5 U6 f. f; J

    2 A& [) s8 \- |( c% _: u# bin传入分割点列表(使用`np.infty`可以表示无穷大):
    ; g( \- X8 D1 S/ O1 C$ h1 ^8 l! Kpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])! F. e6 o8 Y3 z5 t6 G" r- y( N
    Out[41]:
    : i) j. \/ r: t0    (-inf, 1.2]; |  x$ V! a# l/ \4 d8 m5 ?
    1     (1.8, 2.2]
    ' B; M7 E3 ]" K- n9 c5 ?; E7 Y& Cdtype: category
    4 L" d7 [" M' ^5 p3 wCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]: K  x) _+ N- B8 \( P
    $ c- e' l7 M7 d4 [4 h+ H% D
    1: x  B0 i) W1 T( X- ~
    2
    & u2 e: J3 P' x3
    3 F7 x& M$ G" W5 P1 e4
    - `. |- O% L, V: x. w1 x52 h- n: R4 k0 Y$ }' X( c
    6
    : P+ x: l9 t/ E! v. B/ Z! N75 ~( a. `9 U. E* l. ?( V
    8% W1 l4 J" G* s" ^5 O. b
    9
    ; Z: g, j) u" q10
    3 n& t8 C1 t1 ]; f( h11
    7 M" _  t! g* @7 w1 r, R/ X& `0 i12
    4 |0 b# w6 U  m1 K% ?136 M: R" r# \; ^5 c* w# J5 P
    14
    . A, u& K! E+ C153 |# {% [+ f# Z6 I, C( Z* o  n
    16
    0 T' t& u" T4 ^$ J5 ^" n7 ?7 J17+ t+ O$ P) @* t2 i; A* L/ D
    18
      q# r' m- \; ^9 d. `$ D19
    4 H$ _- r+ g+ h$ e- M20
    " H" p3 K3 g7 w4 r4 @6 b21
    7 `; |2 @- g# f* g22- |+ c6 Q; R5 u. w
    23
    . Q4 b4 J1 r+ o9 Y- m; _6 l24
    7 n  f' K% C. n: i0 |6 c25- S# f7 q1 b( o5 i  ]2 r, M
    labels:区间的名字
    3 k9 A  j" y0 ?3 J* Y' x: r6 Tretbins:是否返回分割点(默认不返回)
    / u) E/ m& H; W% E+ y2 ^  Z默认retbins=Flase时,返回每个元素所属区间的列表- @, [4 @* N' J% O1 x8 W
    retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值) h5 c' V3 D" `2 v) A
    , \- i& K& m! [4 T5 x
    s = df.Weight
    6 C; S+ s+ [' C1 P* Yres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)5 i( Q% E6 ^. @/ \# F9 ^+ p+ h/ P
    res[0][:2]
    % `5 S6 c2 e; Y! ?
    + W9 J4 U" @+ W! ^+ a$ S4 P; ?Out[44]: 1 e# @  C/ p! x
    0    small
    3 ^( S+ J( T: J! T8 j: t1      big
    0 B$ E$ o8 B7 I. T# n6 gdtype: category
    0 z* K/ a$ n% O% ?Categories (2, object): ['small' < 'big']1 H$ u. X  P7 O2 F3 g
    4 }: p4 O4 B# n) T$ s- A
    res[1] # 该元素为返回的分割点, k9 [) Y7 ]4 M. v0 v6 v
    Out[45]: array([0.999, 1.5  , 2.   ])+ k* r1 x' Q0 l- [
    1% y1 h5 X" |5 }4 X( p
    2
    2 d- O3 z$ Z% a$ Q% A3
    1 ]3 U7 T- O7 ]; e4( @: `0 i( v0 X  E) c
    54 [7 T) t, _) @6 M( O/ z9 }
    61 J$ j: J8 ?0 |$ ]3 [
    79 Q* x: M( W# K2 @/ v! @$ X
    8
    / f' \% x0 b+ K. c1 n& a9 B$ D/ {9
    - f! m+ U  u4 @! @1 \104 u& I8 E2 A8 }. R1 l( B
    11* p+ Y+ I3 Y" _; ], S8 ?, b
    123 W) {3 l( J8 L3 Q8 r+ o* X
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    " ?* t# |$ L$ ^# o$ F2 w: _5 b: G9 x- Bq为整数n时,指按照n等分位数把数据分箱. D8 V$ ^; |2 E8 t. R& u
    q为浮点列表时,表示相应的分位数分割点。+ X& S$ R! W3 L" ?
    s = df.Weight( j) y) v, k5 H- N' r! A& N
    " ?( e% L+ m+ P
    pd.qcut(s, q=3).head()9 i! L1 c4 G7 O% ]5 X, S- Q8 M. s: {
    Out[47]:
    0 j$ o9 W  J& G4 @0    (33.999, 48.0]+ l$ F6 q2 O% ~4 h( w1 m- Q- u# {
    1      (55.0, 89.0]& \/ w8 b2 R4 V9 e4 {. y
    2      (55.0, 89.0]0 H- _$ B  l/ ^8 o
    3    (33.999, 48.0]/ C9 y2 W: r% v7 M$ I
    4      (55.0, 89.0]' l9 ?- D# G& |- F! s8 G
    Name: Weight, dtype: category* J* E, t" j+ w! i& h2 c  h$ ]
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
    0 T' r+ V: d- G# `
    : `3 T. s/ _2 Q4 g/ Vpd.qcut(s, q=[0,0.2,0.8,1]).head()* e/ |8 _4 v. P
    Out[48]: $ _" h  s  _" J# \
    0      (44.0, 69.4]  I. E1 T: g$ u4 C) u
    1      (69.4, 89.0]& w! S! N8 ]4 x% g
    2      (69.4, 89.0]7 X  \# }& n. p" P5 K/ s% {
    3    (33.999, 44.0]- t5 f9 D- g. l' X* Y* _9 m
    4      (69.4, 89.0]' w  S& J  r1 @& r# F+ J. ~4 w" K
    Name: Weight, dtype: category  e5 N$ D' r# r: C" d
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    : ~) ]" o" @, p& @# n& R7 K& A* }" p. z
    1, f0 t. V- w  h
    2% U, L8 q! N- T3 X) x
    3$ U) T' e! }& o2 ?$ _. r- m- `& B
    4) }- l! R2 B! ~* f8 O; B/ j
    58 |- u! B9 C6 Z$ c* [2 Y- s: E
    6' z" {! ?0 o. V+ n
    7
    ( ]1 {$ H- ~  r/ Z# |# H85 c4 t/ x% C- w2 m: Y7 h
    9, |7 ^+ Z0 R8 M# m: C
    101 P& L; L& z" }7 I3 l, Z' Q
    11
    & F$ d) _! ]& o1 T, r8 y( F122 [; s) t* ?7 T1 X9 L
    13' R8 R  {# c7 n
    14
    2 V' o) P2 L+ y15
    ! d" @8 y$ ?8 O0 ^16* m! m# t8 d( x2 ~( r; E" U
    177 d) H; d7 I" a; l3 e
    18
    6 r- o; l: D% e# h4 K19
    0 x! j, H% U5 U2 j20, `) o1 C4 i& |, U& l
    217 \3 O1 l% G, J7 C
    9.3.2 一般区间的构造
    ) p% ~8 ~4 w; Q  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。5 x2 v! ~* ~0 ?# G$ m
    ; Z) |! S) `/ I
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    1 b4 b3 L! J: v5 r+ y  A: K% Lmy_interval = pd.Interval(0, 1, 'right')
    0 @) N, F& k- u4 l. f3 W" \  [5 r6 ]. i; T
    my_interval4 d& o! k9 [1 x+ D% t! d
    Out[50]: Interval(0, 1, closed='right')
      r. G. Y4 D: `" I1
    5 ?: J9 @2 q0 j2
    % |' A# @% w. n+ S0 c, h7 a' f! u# ^3* s& o( g) {0 y. o/ u( v" Q/ W) [
    4$ r  l# O6 |! r, X8 V
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。- @2 F; v0 @4 l- N1 h& I+ s
    使用in可以判断元素是否属于区间
    * ]4 ?9 @) _- y9 }0 A* G: A- B用overlaps可以判断两个区间是否有交集:
    0 \) `5 o$ k4 `5 U5 N' y: _0 d0.5 in my_interval6 e5 g" Z5 s- N+ l* `8 I/ ?3 c
    - w  Y( C0 p( }6 L) c# e3 `
    True
    7 c0 J& n$ k7 E1 B/ q: P1
    7 {9 Y4 v7 m, J5 a2
    " K0 `, ]% N  P36 b1 B6 S2 P8 X; m- D$ ?% [
    my_interval_2 = pd.Interval(0.5, 1.5, 'left')
    # V7 T7 A# c7 y7 g7 m6 V! D+ `/ Nmy_interval.overlaps(my_interval_2), l) @! c) }$ q! P5 n

    9 v( R5 v8 t! ], _3 xTrue$ M" n9 ^8 i" O0 D0 C% p
    1* z. w5 k: B5 x
    26 U" J: x6 U8 h) W, f
    3
    8 k4 X' d) R% X$ e7 j, Z4
    2 I8 W- J0 G) V5 P: z' O  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
    4 }8 |/ P: m& ?- x7 c
    7 j9 c/ M! g( |. |0 y7 K  Xfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:4 `/ v9 J; E' u( K6 w" L
    pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')5 }  r% j# B6 B5 d) B/ G4 P0 C
    , \, ^( `8 J) l
    IntervalIndex([[1, 3], [3, 6], [6, 10]],
    , Y9 P: V) ]1 s. c2 `0 c               closed='both',
    $ |/ Q4 {  p( C: x6 `               dtype='interval[int64]')4 k6 O) P0 e; |" o! T0 m
    1
    ; U& B5 Y9 L# I+ b2 J1 W  K2
    & D# t+ @* z6 C! o7 }3
    $ a; l" W1 T$ @$ Z7 f4 z) m( q4
    & p4 |, h1 m/ C) O56 x5 v& h7 N  C0 T! ?& D
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:/ K4 G' I$ |& Z4 G$ f% Y" A+ |
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    ; S8 \' [/ r& e' e% }& d4 R) F& _5 {4 c! n: T2 \
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
      L+ v( E9 d, _6 O& i( p4 E* @                  closed='neither',
    * m* }& e3 x- ]8 ~% T, H: J                  dtype='interval[int64]')
    # X4 q: H% P# M1 j5 C1& x" T( D( T1 w" q6 U
    2
    9 n7 l0 L5 X- d8 ^% f5 I, S6 r  L; ]3
    . E/ W* w2 X; I4- u: q% x) @8 a% r& o& q
    5
    + c& t6 M7 L* |. n; w0 W" `from_tuples:传入起点和终点元组构成的列表:
    $ r' O  l0 p3 Jpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    : ]- n, o+ H" ]
    7 y& H% U" P* n& C, m9 vIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],! q/ f9 F$ z0 M( i
                  closed='neither',% F* v$ V% c+ ^
                  dtype='interval[int64]')* Q" r- l4 F# S3 B2 V" s1 p
    1
    5 e- a( z3 b( J+ t28 Z% c, v0 x% m9 O& L; Q4 Z7 D0 I
    3% g, {1 [) U; |/ B* Q
    4" y) d7 T% `# l% j+ F
    5) C$ b' G6 m8 }/ I, [: C. c
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
    * y8 x# W0 }! ~. D# L% ?3 Xpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数- y0 H+ A* J' h+ R+ Z
    Out[57]:
    ) M+ D% z+ t1 f6 A9 U# m4 aIntervalIndex([(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]],6 D+ q- ^8 a, Q2 q
                  closed='right',
    . R  N% O9 P5 @1 x# [/ [              dtype='interval[float64]')  V  C' n8 c) }( c% c
    3 [5 r1 |4 v6 X/ J9 Z3 j
    pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
    / f* u. d2 X2 z- }7 k2 X6 _$ o/ oOut[58]:
    ) J7 i4 P; o  P- i  o0 `' fIntervalIndex([(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]],
    1 I" o8 ~3 T/ Z0 L              closed='right',
    2 D- P2 _# X1 ?8 Z* D              dtype='interval[float64]')
    0 D0 ?$ g9 k; ~. |$ U* u5 H1" P% _- }8 |! t0 d; j+ u1 g8 y
    2
    / w0 L8 y4 Q% s; C  r3
    / y1 D" A  k+ e! v; }0 J- u40 e* T% L6 s, ~2 [% `
    5
    5 y) c# P. K& w* @6 m& R6
    7 l4 `# H2 T1 `9 x+ {. Y7: O. @5 T! \. D3 l8 u7 x$ L
    8
    - }; j0 r# Y1 h7 I  m% h- [. Z9% b2 x/ t5 o  ?4 {0 Y( r
    10: L2 G- l/ o; d6 K% I1 |2 x
    11
    7 J4 Z9 x4 q) m: M2 h' T! E/ k【练一练】
    ( i$ B) F7 \& P) i9 o% v# \9 B( M  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
    . q. q: c  c- b7 p6 p% ]. _0 G* n3 M+ k0 b
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
    * n- W0 F+ K8 d$ G/ i' Y& r6 O2 c
    9 L# ~  K$ h9 a. \* s5 B5 V% \my_interval7 O4 |8 I2 V# o; Z
    Out[59]: Interval(0, 1, closed='right')8 I9 r( H$ h3 W; _' _3 p: A
    ( s, r$ P$ `; F% m) X+ l) C5 N7 S4 C
    my_interval_2( g; K5 M# Q! P
    Out[60]: Interval(0.5, 1.5, closed='left')
    ; [+ |& Q+ n6 o5 \$ q( j4 c1 Y% c( _& V% g7 d1 l
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')
    ( p+ }! d& G7 R6 H* R" }Out[61]:
    9 G% ^% W2 j4 Q5 s4 RIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    3 g3 R% j+ ~+ ^; h              closed='left',$ Y! o: W1 w+ |/ y
                  dtype='interval[float64]')
    # x- [# j3 k6 u6 g4 L) q6 [: v" O+ W* M1
    " l+ H& K5 c7 x! d/ u# s, |4 [1 V2
    0 k1 J* M6 A+ x3 X% P3
    5 W0 y+ _0 h3 ?' `4
    1 t" u5 ^* v1 a" s1 x) y5
    & z& g4 D4 U7 J% R9 \6! s9 t9 Z  w. J* @% u- y8 o7 h
    7
    / m; X# _2 a, Y2 x% I8
    ( u! ^' |( l7 b# ]  u4 S  E9
    1 ?- c/ u6 D# B; X( u; l" |0 I10
    % V) @! m* |% M; _8 |11% y( p* N* Z9 G( a
    9.3.3 区间的属性与方法
    ) L4 H# ^3 m; P! E  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    5 T3 v8 e6 s4 g% L
    ( i2 o; B$ j$ M7 {7 Ls=df.Weight" |1 M# m( k3 a
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示  `* @2 t/ a/ u, j
    id_interval[:3]
    - _% c9 Z9 |5 o' p$ ]2 l, C3 O
    ! L; O2 K3 C: C4 Q6 @2 q1 ^IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],9 u9 ~7 f' B+ }
                     closed='right',
    0 t7 N8 f& Z. V0 x, d3 A                 name='Weight',* z+ {# D2 m, e: q$ V
                     dtype='interval[float64]')  Z. X* F, i& n. e
    1
    6 w- g, a# z, b' t) N$ U0 M1 d6 u2& J1 c6 W% u, O0 J6 U- h2 k
    3$ v- h1 }5 f, N
    4
    ) [- S3 P, r3 q5
    - ^; D- B8 @, W4 E1 _6# U/ d9 d2 U7 D" r) T
    7- f$ p# i9 c+ K: e" N! T  u5 a2 u
    8  o: ?  q/ s% E/ |& j
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    - c. p$ W4 _+ I* o' f, F, r% \: Tid_demo = id_interval[:5] # 选出前5个展示
    % @5 t6 R; g" I: M
    " ]4 u1 Q  [/ [7 b8 lid_demo
    - ~) ]8 x5 d) T! C+ u# ^* A- [+ EOut[64]: 3 H3 b+ {3 @/ {/ W$ o8 m, W' \& R
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    . f* P1 c9 h0 _7 V# p, A              closed='right',
    4 D- E1 @7 ^+ n9 J& g              name='Weight',
    $ J& [9 h( k( I2 {              dtype='interval[float64]')  G, [4 S% A: H) |4 ]
    0 W  J) }# z3 `7 N5 h  T
    id_demo.left # 获取这五个区间的左端点, v( s$ z( ~* _# ]
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')) T& m8 f" C* e7 z) F* N$ q5 X
    ; i4 s  E0 D9 p# n1 j$ t5 j
    id_demo.right # 获取这五个区间的右端点
    ! K1 Z6 R+ b$ T+ @Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    : p- f4 V; u1 g( j9 d/ c7 R; t
    0 C1 r# ]3 m- U# s" Mid_demo.mid
    6 C% d# i: t: Q4 tOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    5 p9 V1 }% E& S/ }2 ]4 d! s& n$ q& y4 U5 n7 m
    id_demo.length+ N3 q5 r& k! n1 [! J
    Out[68]:
    : g/ l( f; {" C3 h) c- R- MFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
    " p6 k4 V+ K- @. p              18.387999999999998, 18.333],/ ]# q. |+ ?/ y8 D! w8 M+ j! |
                 dtype='float64')6 _5 e) o4 Q/ d% c
    / t& s2 q; R( y* c7 p7 y
    1
    # ^/ T) p3 A/ X! y' w# q) k5 B/ o1 x2
    # _8 d$ d) b' R0 |3
    ; d: P9 w2 s' b% B4 M. ^7 }4
      [, t1 e) ?! o+ h: u% J52 {  G& U; c8 O, m: X% K! w
    6( [4 Y- Z- q* m' T& X) x2 o
    7
    - f6 _9 Q6 d3 u9 k' A8
    7 u' i0 w0 q2 N0 V  T1 t9
    5 E0 f# Q9 c4 E( q10
    : f& l5 `# R; B5 u11) c0 x1 ]# w2 J2 A0 }" }
    12  C; R* a! Z' i+ }) Z+ x/ ~) H1 ]+ V
    13( H6 I6 g$ e( w1 u/ T# e
    14
    6 N+ ^* D  @. C* z- M# B( d15! M  d7 L: O; j! V' q7 m' Q
    16  i2 X( F% O/ ^( O# ]1 k3 [' w
    17
    # \; J6 S. B  ?$ w0 i8 B8 F7 c$ L18
    0 v; U$ P# `6 q) ]4 H+ c8 m19
    5 G4 _, e( n/ @* ?2 q4 _, r20
    2 J1 v' K8 u( a' D" M+ O$ K21
    1 r$ x, z# e; C% E5 g# m22+ V' J9 x' M. J  d& ]
    23
    1 M7 _7 Y7 r/ f* B# ]- _! nIntervalIndex还有两个常用方法:
    ( F. v( h5 y  A6 scontains:逐个判断每个区间是否包含某元素
      ^- z, c2 J. X# F/ ~9 L9 koverlaps:是否和一个pd.Interval对象有交集。& e5 i4 R$ J# K4 U# K3 A- o
    id_demo.contains(50)3 |5 N. r, F) U* Y
    Out[69]: array([ True, False, False,  True, False])
    $ Y7 j0 A1 N2 W" z5 w
    , X/ V( }& Q* }: X" W7 Aid_demo.overlaps(pd.Interval(40,60))) J# M% ?' q& Y! X: @9 y
    Out[70]: array([ True,  True, False,  True, False])
    5 W, C5 d7 Q5 @9 A- G2 }' J4 Z. d1
    3 M3 s: m" o3 p2
    ) |7 ]' D- f) b! f7 @3% P- a( \9 a+ o/ ?+ q! |/ a
    49 P3 N* Y2 a" u! C6 v0 N# W: {4 n
    5
    " ~# r8 r) u- I& Y8 |$ o7 ^( h9.4 练习
    7 w5 G: w4 q6 p( w" ]  Z2 j$ JEx1: 统计未出现的类别" C1 b: \% u/ ~
      在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:( N& g; q5 |# b+ u

    , C6 s$ O3 a# q4 N! h+ x# X9 K# fdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})8 ~0 E* _/ N! T5 D% m* W) v
    pd.crosstab(df.A, df.B)) M) F4 q9 ]  S, G. [! ?+ t6 _+ p2 n2 ~
    4 {' q7 z" P5 L0 A6 e7 i
    Out[72]:
    3 H3 u! t5 ]2 C8 M" kB  cat  dog
    ; p( K# m+ A8 oA         
    8 m2 C" L* D3 L: r7 oa    2    0) O0 ^+ {2 t! H! H6 [# p( r
    b    1    0. w0 H9 n" g- T6 N
    c    0    1
    % I* L; ?1 F0 s1 d3 Y( V5 c14 S& J0 g/ D* G2 e# h" a( V4 m8 R
    21 M, I7 B: q8 g  |9 P
    3
    2 g) b" a& S$ v3 A$ F7 q3 z4  V+ m7 s0 _' `2 m% _4 Q
    5
    ! r. d+ |% ~; Q6
    " G: t+ b: }% @77 E1 N7 i9 B7 O8 Q+ s0 l
    8
    1 x( R" H# I, B$ G/ i) b( ^9 a9$ K% j$ A8 G' g" `4 d2 R
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
    0 Z' ?6 W. F0 w; e
    5 d: L2 k6 |# G, P1 M* z2 Y7 P9 \% Jdf.B = df.B.astype('category').cat.add_categories('sheep')/ j7 U7 P0 {! X9 M( J
    pd.crosstab(df.A, df.B, dropna=False); }5 [' v7 F+ E* Z1 T2 p
    2 H3 F  L% e2 g/ q
    Out[74]: & g' i) r. C. H3 |. |: {
    B  cat  dog  sheep
    * h" [. Z0 N3 Q3 {( Q& S0 SA                 
    ! S8 x; b' j$ G3 q3 `6 I9 \0 i1 g: za    2    0      0/ b1 W! U) ]; Y/ b' Q
    b    1    0      02 X! N3 Q7 w2 X4 F3 n7 [; B
    c    0    1      0
    # x0 l% z5 k# z) E8 ^' r% e1
    ( U6 `3 Y  n9 N# ~, X) t, G% \2$ t+ Y( M6 e2 K- t
    3
    0 C1 c9 a+ T9 k5 K7 c3 ^4
    ' |" J) g3 h* L* a5
    & i6 ?: U# }8 A: F2 v% j6
    2 X& \2 H6 B$ o8 z4 ~72 f+ s3 x3 r$ O4 `$ y0 c
    8
    " L8 ^5 m3 I7 U, Z+ A9
    2 c+ g& Z2 P9 t3 X4 R请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。9 }, w4 A* o, O

    & B' Q, l* X% `: g7 w) ~( rEx2: 钻石数据集
    + S# T" t, P7 t  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:" [5 ~% p! b) y2 r# j" |' n
    0 a$ w- p, S1 ^+ H- ]
    df = pd.read_csv('../data/diamonds.csv') & i; H/ y0 g, W  q$ \
    df.head(3)* m& Z5 c* ~7 C) @  |

    ( [- m. c; U! B0 t" P' DOut[76]: 4 C' J. i$ d# z, j: s
       carat      cut    clarity  price
    # G3 k6 x: ^  B( `0   0.23     Ideal     SI2     326
    & Y: }3 t1 ~3 n9 H$ u1   0.21    Premium    SI1     326" a( b5 R% _8 _9 p9 J, j
    2   0.23     Good      VS1     327# J  G; ^# w& r/ ~# H; ?% y
    19 U) @" f4 K/ B+ n$ j
    2
    2 ]4 b2 A, g" H' t" F36 b; w) ~5 ]) X2 T# X8 G1 D: a1 \
    45 A! g% e  M1 f
    5
    $ M, q. @, R, Q! g% g& ?64 G/ z7 a3 W( ?* _( ~. O4 n
    7% R% K- L: A, n5 m* c1 {
    8
    # b2 C) ?' L' T7 x9 f分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    " Q& D4 Z5 k( [: [1 }9 \钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    ; u8 j! G. s' e0 i分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。) D  O4 a: d1 [4 Z3 d7 W6 D# j
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    4 d# {1 M+ B/ P: M- Z; ]1 q第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    $ ~4 O9 n5 `: A: ]对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。4 E, I( P+ F  Y* j6 n( T& L" r- u
    先看看数据结构:$ f" u$ p  N5 l$ |* b! B

    2 X4 K- c7 x7 l$ bdf.info()
    1 d, I8 k0 n9 `) e) O) w7 a; |) c$ Q9 qData columns (total 4 columns):$ G% U2 _2 j0 B4 u2 G- U/ z$ c
    #   Column   Non-Null Count  Dtype  
    1 I' N! c/ [& w5 K6 V3 m* s---  ------   --------------  -----  9 @0 V: {; e8 j! R$ {. I% p+ |
    0   carat    53940 non-null  float64
      e* w; @5 Y: V2 M 1   cut      53940 non-null  object , G4 w' O0 p2 _. @% [
    2   clarity  53940 non-null  object
    4 c- Y' z* Z0 {# d( h; {+ s 3   price    53940 non-null  int64  + R8 I! ?  H8 ]' K
    dtypes: float64(1), int64(1), object(2). c( ^. R0 j7 i0 q) w
    1
    5 v; P$ t; s$ ~2. A" m) c/ ?( E. b2 K! z
    3
    ; k) }% E* `  C  T7 f; i+ S  s4
    , v9 x' a, u; b0 i  |6 d& Z$ Z5; R( d  `8 q1 R! n% n; ~& f; {5 J
    6
    * G# y5 R# j% ?( \7
    , Z; F* M9 M! S8. M5 n- @6 a7 L- p9 y. ?4 v
    97 k& m0 ?3 v3 f. `0 q7 d: k  x4 P
    比较两种操作的性能% y  H2 E. f) ]) }& t8 H
    %time df.cut.unique()" m: h9 I: R( l8 t6 M, G9 K9 r
    0 c6 @" p7 B  x& ]
    Wall time: 5.98 ms4 N+ ?' U, t! {% j% m$ q
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    # g. ]) s) U' d. G1
    8 c% Y+ x) p' H- U2. [/ t5 \2 U8 l# J" u2 i9 I7 Y
    3: l- x! C3 T: {" q9 a! E7 E: l
    4
    & J- n/ b2 q, j3 }" O" B2 d3 ]%time df.cut.astype('category').unique()
    5 Q5 j0 ?) A+ k  Q& R  s8 n: I- n* k
    3 Y3 f* g1 W8 ]8 R- LWall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    ; j$ E& r" j, V; A7 C& ~['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    1 ]5 u, s1 c$ s2 E$ i! `Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ! k! b& o/ h/ s1( R3 c+ n, i' }/ O
    2% s" V) ]! x- E& J
    3- v; A! }  k6 \
    4
    0 `- f4 c3 ?1 `; P9 ~- {5
    1 @) g6 T- r. q9 i6 A: Hdf.cut=df.cut.astype('category')
    7 A$ A! }  }# q8 i1 @%time df.cut.unique() # 类别属性统计,2ms
    7 c; I) N8 {3 o7 I) T
    + z0 w0 r5 L$ v) aWall time: 2 ms
    6 W( E# M7 B5 e' {0 `; S['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ; M: d+ l, h3 V2 u, w- L" E* N: qCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    # m, k4 s% \3 ]' @5 K6 _: d1
    " e" r! i: c; r; t1 \2
    5 K3 _' Y% s% H- Q4 z3# h! j, q; t# v' V2 y! `
    44 {- P! ^9 E# i1 j$ l4 X7 d  F/ c" c
    5
    4 c6 y' Y; D" b0 G9 `64 I* i' `% O5 d6 m
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。7 l  l! t  A# Y$ [) D, a2 G/ e) X
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']& r) `- X1 w! M
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    % S$ o2 d! O7 v- q/ J, ~df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换! E9 y2 l3 ~9 ^+ |) K* w; j/ K
    df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)9 r  Y5 m; |; W& L# i2 z
    $ @- ?8 b  Q1 b( D: G3 I$ \
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    ( `0 G& X4 h3 p" o) ~2 i0 S5 d' y4 S8 t$ c+ D6 u6 x
            carat         cut        clarity        price) m$ \6 `' O- S7 ?
    315        0.96        Ideal          I1        2801& t$ n" a3 M! U% x* x; U
    535        0.96        Ideal          I1        2826
      A( _0 H8 T% c# i; Z. D6 w551        0.97        Ideal          I1        2830
      X: j( m$ J/ z$ ?8 |$ j10 u) @! B( {" ?- k% x
    2+ Q2 c, C* F5 ^  ~
    3
    1 ~6 P; v! X* c; l" \& x0 e4
    ; i% k8 r9 r& c5 k4 z* E- M5
      @0 d5 x8 N+ ]$ B+ X4 E/ v6
    / B" G) S( Q+ ^' |, t+ i6 n* P7$ V* H5 l3 |' X) T
    8. F( I% I- A$ k) W
    9
    7 ?2 O9 B$ d5 y9 y$ ^% P$ i9 r10+ t' @. r* K  F, T4 T& o- T7 Q. b
    11
    3 m- K; \5 d9 k$ E分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。4 l6 g5 j% j6 G) Z1 H+ F  q
    # 第一种是将类别重命名为整数
    2 c: M) M5 P4 I. P' h* Mdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))) F/ h3 ], {- V
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))3 c' ]; {. K/ y7 t$ l- l

    2 O5 g; `/ X$ D9 ?9 j3 ?df.cut=df.cut.cat.rename_categories(dict1)  F. J: W+ ^1 y& E1 e4 S# W7 S
    df.clarity=df.clarity.cat.rename_categories(dict2)
    6 }& ]( g# @# Wdf.head(3)0 `, B+ Q: l* p8 l  Z2 c

    + _* N% r, _% {* u9 v        carat        cut        clarity        price) K: |  d2 Y: m% o
    0        0.23        0          6                326
    ; T3 J2 ~5 l! H# {* z. F  {' i1        0.21        1          5                326: F% D$ a) u9 E) ~. _$ \* l
    2        0.23        3          3                327
    6 S4 j. m% e/ b& U1, M9 F+ N" a) M" v/ `  G, E
    2
    1 f; ?( C' P  y* g5 V3
    3 u5 }3 X1 {' W  J4
    / s7 ]' k) Y. z4 g! S  v6 g0 I58 x0 z3 ~& I* f( `% p
    6
    3 B% h& N0 V6 ]. F. a+ ]! T3 h  _+ m7
    / V; }+ n8 Z9 M9 P* S: e$ b87 {0 S0 V7 ?- V. r% Z8 N6 _
    99 V) e$ l% ^0 @( F9 j! X# B4 E
    10
    1 a% {# f& {8 ]* ^. e11
    ; c! w+ d: P* R. n8 u  f12. L; w, X5 u: E% x$ |4 \2 c
    # 第二种应该是报错object属性,然后直接进行替换
    4 Q+ i, h4 r* i, I2 M6 ?- W6 Jdf = pd.read_csv('data/diamonds.csv')' P* _$ X% K6 l9 @- |
    for i,j in enumerate(ls_cut[::-1]):" {7 A7 G) P" C: L% P0 N) |2 h
        df.loc[df.cut==j,'cut']=i ! Y& e. @% g0 V" k- C! |/ Z# l- ~

    / _$ s0 v3 V) h) v0 i. k: Kfor k,l in enumerate(ls_clarity[::-1]):
    + A5 t1 `1 w! w2 ]# s4 y    df.loc[df.clarity==l,'clarity']=k, j2 e" h+ X$ d% W9 z
    df.head(3)
    2 |+ _+ X5 L$ e0 ]! X
    8 i: c0 S  |2 g# k0 f        carat        cut        clarity        price" f# ~! k" U. E  g
    0        0.23        0          6                326( g6 d( Q; q7 {9 d) g( C1 m
    1        0.21        1          5                3265 d# d( E% T7 j  ?1 D
    2        0.23        3          3                327. K& a+ [/ g) N
    1# i: y3 c- H* E; D3 L; v  g
    2/ T% l7 @) n) B+ W
    3
    + \8 Z4 e- l2 d8 j0 j# ~6 T4
    ! ]3 T+ a! v; ?1 y* x6 l5
    & K% |0 k+ f: |/ W" z6
    + E& n' I# y  L" H' P6 A) R7, e4 G$ @1 G. r/ L3 H4 f
    8
    ! h. V) i' p$ R/ \" y9
    4 }7 D& a: [$ n4 m$ |' m10. r5 A: e  J$ U3 ^3 K2 ]
    11
    * O  {! J: w! `9 C) @& x12
    , i8 l" ]2 \5 _+ f% N13! p( L' A, `; J  ^* L4 Z
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    ! ?; L' E; x; _! {# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点) {" l+ T, {. a1 `: U; y
    avg=df.price/df.carat; H: T- L  ~1 I% a. ]8 B7 z
    , l7 B8 |* x: r  [! k
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],/ \6 ^/ k, G2 f9 `
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]! g4 ]+ b( w$ s  x: d( M# D8 X

    5 t: z4 J% R7 O# idf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    / X% H( o0 b$ D) H. {/ c( P                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]! A' V- P) ]6 Z7 C  d1 ]; p
    df.head()
    - q+ G$ ~( O+ f' C( d0 x
    0 J# s1 T( z5 u" S9 K, [0 X        carat        cut         clarity        price        price_quantile        price_list
    9 J$ N: T+ a+ Y" f8 y0        0.23        0                6                326                        Very Low                Low: I: M2 K* H  E# ~3 O4 D2 U8 c) o9 z' a
    1        0.21        1                5                326                        Very Low                Low9 ~/ D# H4 j; k" z* e. k
    2        0.23        3                3                327                        Very Low                Low
    # K" k( B1 b% z8 C8 Y; q3        0.29        1                4                334                        Very Low                Low
    9 _3 P; d* x' X6 @  {' r4        0.31        3                6                335                        Very Low                Low                                       9 c' X3 n! N9 {. R- j# J; m8 r

    ! B/ j0 O# q, u- K# U8 ?- e1
    - j8 Z1 [  y- }) i2! I8 R4 o; l5 H4 R
    3
    / R3 _- z6 f7 Z! Y0 Z/ G4
    8 j* ?9 F( o6 t7 j9 p  B7 v& z) ?5# i9 [0 ]' _8 a
    6
    # h- I& E  R$ Z/ [& ~( S: k: R$ K7' ~' G* N* a4 ]1 Y
    8( j6 f8 B8 B3 |
    9/ k8 I( F4 i' g8 ]  n+ m/ p9 y( e
    10+ Q) h4 o; J  {4 \, I- `2 Z
    111 e2 g+ ]2 [3 I# W. }: @! n9 \
    12
    0 t# x" S' T$ \( x7 W13: t9 C4 i2 E% p% b
    14. W. E% n' r* r7 n
    154 @, r' \' r. X% r9 D, f7 C
    16
    , i/ G5 _/ n" P7 ^% c. W分割点分别是:+ x& H8 P  O) T/ l; v* [+ S
    3 A6 Q5 N5 _& f5 A& s
    array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])/ H  T, h/ S/ D4 Y
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    , i; v* k( U5 _- A- S: w1
    ; |* D- D4 G9 H: r+ h21 o: Z/ V" M! t' K9 S" Y7 C" T
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    5 P; V+ _; R8 Jdf['price_list'].cat.categories # 原先设定的类别数2 z, k3 P* n, `8 x
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
    # a6 @9 a, d) _2 L1 K
    2 D( D( e+ k; |1 Odf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    8 k0 T0 ?$ [! D  D4 p6 h- V) m/ Z( X; QIndex(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    ! I6 T. u. S, |% |18 d9 O% u+ K8 J: c
    26 L. s5 y. e$ \9 h2 O! z; \3 [
    3$ B) {; Y: @  _4 n. Q. s1 t" O
    4
    1 R; g8 ^: l% I7 v( B. T5( i7 ~' N2 H! p( C3 f  e1 n* Z
    avg.sort_values() # 可见首尾区间确实是没有的
    6 @+ S; \# |- }& A: d& D31962     1051.162791
    . i, K: ^( V' {/ q% |1 V15        1078.125000
    9 I& e$ {+ C% [5 T, p4         1080.645161" N4 k4 a; w" S" I! F
    28285     1109.090909& Z3 U3 F4 O( V7 C1 D
    13        1109.677419* Z/ S( x/ D: j! W5 u4 I
                 ...     
    * t$ I, D- Q$ O4 F( \1 q8 ?8 X26998    16764.705882
    $ h/ S9 b0 f2 p- x, ~$ L9 D: Z27457    16928.971963
    $ q. f9 f$ `. E5 c) J27226    17077.669903
    . ~: K* ~& F/ Z27530    17083.177570
    ; Y5 j: C0 v) U! Z  M27635    17828.846154% Z# h% h3 N- {. }* M) r) b+ }
    1
    9 z7 _  r  m' R# K0 H' Z7 y% @( ^20 x7 E. Y  f3 w+ z  u
    3/ k' [! ?$ C; Q4 l  y
    41 K$ u1 D0 E0 e
    5- W. z2 e7 [; j" J* T4 I
    68 P+ u0 c: R6 \8 }& n, A
    7$ u! X9 C: z  i$ g5 j
    8
    " P2 s! ^8 P$ Q, o- r9
    - F& L# G$ `7 y& B7 b10
    : H' u; P2 _: s2 G0 S: ~11. F; A7 I( v& C+ Y2 ^- }
    128 H- H/ h, b0 N+ h0 r4 Q- k
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。+ B/ X: u* {- H0 Y( M- b4 t: S9 b
    # 分割时区间不能有命名,否则字符串传入错误。2 _8 w$ `6 K5 M- W+ n3 \% H, }6 Q4 P
    id_interval=pd.IntervalIndex(
    / Y4 l- {3 {9 O6 p    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]6 {6 ~% [- G& ~& ~* k
                                )0 D5 p& @4 l" [1 d  n) @6 E
    id_interval.left
    1 U4 e" w- L: Z8 F9 Bid_interval.right3 H, x, a% Q' w0 q5 t& h  d
    id_interval.length                           
    9 X; D$ l% M: J* f% n* M0 g7 Y1
    6 I- p/ `5 t" J- n9 u2& v! Z  v' {# G5 I, d6 B7 s& k
    3( j( u4 L/ W" L3 \4 Q$ K8 L
    40 Y7 N. H( m. ~# C
    5
    4 C6 l3 g: ^* w  V% r- u1 Q6" h' P( H& }' x  {( p
    7
    $ z( x' t9 N2 W- p( D第十章 时序数据
    - m5 ^6 V+ g  b$ a7 e+ bimport numpy as np+ i+ n# N% C/ E" U+ G1 P
    import pandas as pd
    ( V1 L/ g( M( c3 U2 B* A! H; e15 ]* M. }5 U$ ~/ r% p2 u, `. B
    2. @' H7 k$ R- F, @% E
    1 K( q; y) S) x( W6 `

    6 Z0 U& S: g# w10.1 时序中的基本对象2 P* C" }$ T/ d. _" |' K
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    ( h" L9 B  t1 l, ?5 U; ?6 F6 e) T/ O5 }
    会出现时间戳(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的简写。2 R, F" o! i4 L9 e/ |9 E3 A
    % I% F2 d2 g( Z
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    & k! C- x/ C/ q4 G: o( v! _
    3 r1 y' d) v  v- v会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。/ K' A6 L: l  c9 ?9 K0 h3 Q

    1 r7 ]( p/ m1 j/ n2 A9 X" f会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    # F; p$ m- N* G# @- r% r. {# O- k
    . B6 D9 \& ?: i  o- W. M, u+ n5 R; X  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    8 M: L- m* u) O( j' o
    $ H) l+ ]" ?& k( t5 U9 N概念        单元素类型        数组类型        pandas数据类型/ J$ t5 s2 q' }9 b- i
    Date times        Timestamp        DatetimeIndex        datetime64[ns]
    & f; u3 i. j+ Y6 L+ J/ }Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]0 j8 N- _0 M8 s& O& m
    Time spans        Period        PeriodIndex        period[freq]
    $ y5 @# Z: H& N5 j3 G+ iDate offsets        DateOffset        None        None
    0 M9 E7 Q8 g3 w% f$ J0 E5 p2 G+ k! T  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    1 b. v5 @! U2 n- ^8 c/ D/ o8 o4 C+ u0 C% i( y: y; |
    10.2 时间戳
    6 i) O( v; `3 Q8 _" [2 X10.2.1 Timestamp的构造与属性: F) k8 P8 b" c% _
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:' ]$ I; g2 M" k* w; p$ ]* z

    4 R7 |/ ^$ d/ |, }! }ts = pd.Timestamp('2020/1/1')8 e. L- j* f( Y/ B% \. S

    . t' [! F0 m" l' J4 |- `. Nts: n1 o* t/ f* s' `/ b3 K
    Out[4]: Timestamp('2020-01-01 00:00:00')5 V- n  l8 [% i, m$ u7 U

    + @5 P4 _% a# b' E% xts = pd.Timestamp('2020-1-1 08:10:30')
    ( [' V; Q4 M, z: v
    1 x/ [* r; j! D+ Gts
    7 n  J" k& W3 R: c/ q! p5 QOut[6]: Timestamp('2020-01-01 08:10:30')
    9 }% Y% s" E& F- n! ?12 k. }2 }! T& d6 {% [/ f) v( L
    2
    3 F- |: x5 N& M8 J/ M) i& @3
    # y& z8 b# o- J  l$ g4' O( b1 O1 {8 H8 m& E1 A
    5
    3 |" z* L) L6 Y: {6/ X6 h% b  I' E4 j' S' O7 q
    77 Z* T$ B" k1 G! }4 c4 }1 Y
    8
    ) {! f) A& h* x3 |- n: ^, a3 n96 j' t. j$ J$ h9 q
    通过year, month, day, hour, min, second可以获取具体的数值:
    - O+ Q9 J" s- \, L2 L+ B( Q- A2 A" _
    ts.year
    , w% x; I( x: D" z* ~/ Z5 ?2 bOut[7]: 20203 X/ {/ L! f2 J, D
    9 {, j- v3 U7 M6 J
    ts.month# _2 K: ]; }" J4 T
    Out[8]: 1: K8 d6 Y  Q+ F% a" R0 c

    + h  E+ O+ Y6 ?. s1 |. pts.day( O+ j; I# p7 M" X  y+ S
    Out[9]: 1
    + A' A6 q6 {; [3 n1 u7 [8 D
    2 o/ ]# N7 u& V, Gts.hour4 a/ X2 d( G2 ?$ N
    Out[10]: 8# R' c& v6 k4 E8 a; b, O
    5 L/ X  v9 G, L
    ts.minute
    7 ^: B' L& S% Q! O6 L: H- NOut[11]: 10
    1 b  h7 X( l, E$ E4 S- q. s. \5 k8 G
    ts.second
      ^/ Z$ l+ C0 ~Out[12]: 307 F4 r0 v$ N2 P  {3 M
    9 ~( ~8 y! p) @4 a- G- i! c
    1
    6 a# h' @9 \8 Z' h2
      j- S) }* `2 r5 U5 ~3+ F1 k* z; p7 Y7 f+ K9 {* U5 [
    4
    7 @$ A; ^( C" T# N; @2 _6 o1 M5/ N/ s4 V+ c( k0 \: O
    6
    ) ~& P$ l9 m# M: u0 `7# r7 ?! @0 E' b* o1 W- v
    87 B" W% r5 w& Q! M  J
    9
    & m6 M" s. }& b101 x$ _8 b* g2 \: s! ~. F5 e
    116 j" b$ c  M" D/ h( S$ i1 z; m
    12
    , \' R$ |- y  I- ^/ X6 H13
    9 A& @+ h: |+ |% a/ Q1 t14
      f+ r6 T, s) }, }( I15, d4 ?* e" K, g+ @# x
    16( y, y9 R" D  i. k& F( S7 b
    17* T- {, H" x, Q9 d; j
    # 获取当前时间
    3 Q2 g& c6 R/ n4 u8 Inow=pd.Timestamp.now()
    1 J& I, h) E3 V: ^1
    7 _& ]" k  ?8 M9 y, O2
    % q" ~- A% U6 m5 }在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    ) \8 ~, M! t% x- d4 ?7 y: q. \( BT 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)
    , @& [8 S/ O2 U7 j' [& \TimeRange= " e' G4 k% r' y# d7 L0 u
    10 # t7 M7 e7 O3 r& Y2 w, H
    9
    % n* Z/ r9 }( f" l; M8 L9 D ×60×60×24×365) l9 u9 T" y9 {' c( p/ \9 H6 \) p
    2
    4 P0 v5 F! [9 `; W, j64
    % R) l. a/ b7 G7 `) n
    ! B* h; P( g2 s6 u" T" W; J$ Y9 ]
    ; r- M8 V' T/ e( i) J8 w2 W ≈585(Years)5 O! l' n2 H! y8 b

    + f5 W! w6 [4 l. R8 \3 ^通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    ! K$ W! y6 I/ m; A* t3 m3 W3 s  K
    pd.Timestamp.max$ W$ {* Z" q# s! F
    Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
    ( k  W+ F/ C4 }/ X  y
    ) n5 X5 l$ u$ f) p& R* w( dpd.Timestamp.min
    9 B& \" G% @9 p) I' rOut[14]: Timestamp('1677-09-21 00:12:43.145225')  n% }8 O9 @% @4 |) E4 d! |( G$ U* s2 ~
    % k) ~& l4 s  g" v4 e8 u" G+ e/ ~
    pd.Timestamp.max.year - pd.Timestamp.min.year
    ' b( O4 U0 G' k4 x; g& sOut[15]: 585* A% N4 T0 w( L
    1
    3 C' j6 {* W/ L& ?29 Z( O8 n! y6 v4 C3 h% ~
    3
    2 L  W0 E( M% Y- q5 x( p4
    ) p% Y  E3 ~, M. w0 i; h5
    4 u- D% D( p) u" p& A6
    * a, Y! H: c! ~7
    4 b' Y9 A/ V# _6 D8" I: A" q  T! {
    10.2.2 Datetime序列的生成
    ' k$ C* ?" H! Z2 N/ M8 U9 g1 n" ~pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,( w& y7 G" Q, Z( Y) C
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)% @! C; f0 ^* d
    1) T% ^3 @- G0 E9 n; `9 C) T* R
    2$ ?: g( @; r4 l: b  ~0 m
    pandas.to_datetime将arg转换为日期时间。! S4 D* ^2 ^2 C( w/ H
    9 C9 G' D8 Z( x( n. {7 }. M: H
    arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
    / s$ A! L) j3 v8 \( Jerrors:3 K* W- b& M  f3 ]2 ]
    - ‘raise’:默认值,无效解析将引发异常. U$ }5 l  E4 [: j- \$ B
    - ‘raise’:无效解析将返回输入
    4 O; v$ e& X  [! s- ‘coerce’:无效解析将被设置为NaT0 a) M' @! J9 _4 T0 N* j
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。* Q4 s% X5 H) T: C9 B
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)3 t9 G( \1 H, L7 R( C
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档* j2 B# f/ e/ ^8 O3 {
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    4 c4 J  T3 g# }+ C! W. {unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。  [* b6 Q; i7 q2 K* [: t/ M
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:8 A" x0 c1 P( h' d
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6']): r& A! W' a7 \' R+ c0 Z

    ( B, j* d1 h: E: [DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
    # ~& U% A. {* \" n8 D1
    2 a) h0 V8 b7 b' g: H( ?: @! y2
    9 u) a+ K9 B$ w/ w  U$ n37 h7 e2 D  r4 }3 \3 c8 P" }
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:" x/ N2 D3 M* M; U

    ; e% j4 l& L/ h% utemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')$ T# e8 a0 k+ s% }$ C+ _
    temp
    $ h" g2 b' F) D' y1 m
    3 p! V; j/ N( Q- w7 `3 O' uDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    % X- ]* h7 K- d* J4 I  U, O1
    + l! [5 Z- H$ _% K2 q26 x" A. j! ]3 M0 H  }, n
    3  a  w  r- S' N+ ?( b
    4
    $ d% }( r  k) X8 u* ^, o1 U- ^  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    3 p% C6 J2 M0 q8 q' }+ I1 ]6 `0 n
    9 e! G* y% C- ^2 J! @pd.Series(temp).head()  Z5 {! n2 x0 T! d5 ]0 {
    - c- f' v0 Z' y8 [! x4 Z+ I+ ~
    0   2020-01-01
    + y% G2 o0 \  |0 V; k( r8 v3 @4 x1   2020-01-03
    & k5 P% k3 k# K3 qdtype: datetime64[ns]6 M$ y# P$ y# [$ o
    1
    2 k- S8 F! {4 `2
    8 w- M+ b+ @9 t3- h- D1 i7 Q2 k, ?0 Y' e3 A
    4% H, V$ d9 M% ^' q; y7 D3 p  G
    5; h. H5 F% p% r
    下面的序列本身就是Series,所以不需要再转化。
    & O+ T9 \! E! l2 |
    8 q5 p# d% g7 v, Sdf = pd.read_csv('../data/learn_pandas.csv')5 c& [0 x4 `7 \# n  R6 z( _. U
    s = pd.to_datetime(df.Test_Date)7 D7 A1 l9 |- h8 c" ]
    s.head()
    1 R0 d, Y# j3 L, x/ \/ Y* [! f7 F( p- A+ i4 n
    0   2019-10-05
    ' |& j2 r! k- q- M# c1   2019-09-04
    0 U5 ~, X6 z* O( D9 P2   2019-09-12+ _, }8 y' w+ W' \& ?
    3   2020-01-03
    ; O' n& E! Y, V4   2019-11-067 S5 w( I- D3 t3 }4 g
    Name: Test_Date, dtype: datetime64[ns]
    - N0 l# e" u1 \) J( h. a8 E4 C1
      L9 k9 z# c! U: x$ V+ _4 J2! `# e5 v: O9 X$ y$ B6 |
    3
    # a; }) t6 {' f" ~7 b3 c" g4
    * J, x% Z1 }$ T2 _! B) B5
    1 z8 l/ D5 B; O1 d6& |- a- @( ~  H1 L! O5 T9 b
    7
    . N- p0 q5 f; m9 `85 Q* V  {' N" m6 l" K* q
    9& N5 _9 N2 u: P4 o8 y) f
    10
    3 k7 R3 {4 L+ J) y* V; d- }; I把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:9 Y; a2 b" h" ?- Q5 G* b
    df_date_cols = pd.DataFrame({'year': [2020, 2020],
    * Q* f9 n0 k. }( U' b                             'month': [1, 1],
    ; \0 W) s( |3 q, g( i                             'day': [1, 2],& q9 G# i; E8 ^
                                 'hour': [10, 20],
    4 Z8 W* {9 C  M/ a9 f# I                             'minute': [30, 50],
    ) y% r3 s3 I9 O& _: c$ Z* K. t. f                             'second': [20, 40]})
    * a2 t0 U/ H  E" \pd.to_datetime(df_date_cols)
    4 u3 H4 m  j" w+ |$ C7 p7 {, q5 ?  O
    0   2020-01-01 10:30:20
    8 V# k& s2 N+ K2 G4 g' M1   2020-01-02 20:50:40
    / C+ q4 [3 X; [0 wdtype: datetime64[ns]
    / M4 v8 L# O% d% r0 p5 p1 A: F1
    # \' Y. c. F1 F4 T7 N  `+ s20 d! W" ?( p- \- P
    3
    7 r4 j" C! I9 I( ~" r8 u3 m4/ L: Z/ P3 Q: O( a
    52 W! y, {! b6 b
    6
    1 \5 D( d, J; ]7
    " e) |1 v8 Z* s: @! F8
    & I6 B) W4 [- b( X9
    1 b; i- I6 V9 u: R108 d8 Q  G* w& E+ s: z
    11
    . a- ~" _, `3 D. G: ydate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:/ g/ M5 [" f; c9 I- Q
    pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含8 N7 L; `' r0 g7 f6 I; p, d$ v% e  ]
    Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    * {6 u0 H! k( T
    8 v" N, {' u% C. n  H" w! \3 |pd.date_range('2020-1-1','2020-2-28', freq='10D')
    # R% r6 `7 g4 T& f5 UOut[26]: . ^0 D2 V; r$ k3 Q
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
    ' z. W2 M; H9 H* }) B2 X               '2020-02-10', '2020-02-20'],1 f3 X0 s- u" |; i' Q
                  dtype='datetime64[ns]', freq='10D'). [. V: Q; o! e/ u& G
    % s3 v3 `; g7 K/ s6 ~
    pd.date_range('2020-1-1',/ K& |1 {# [/ n1 B# H: ?2 Y
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天. S0 C/ G- u% q. [) l' T2 J  v1 g

    ( ^! Y, j3 J/ R9 b. ]  X) N; MOut[27]: # {, g4 M0 s' b7 Q4 R& V
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',8 U6 ^+ Z% w& B6 H' t
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',7 p" h3 n4 e) m3 C- P- \" b
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],$ B) ~/ y; v+ f1 r% R+ f
                  dtype='datetime64[ns]', freq=None)
    ) L3 G0 Q+ U% X9 F
    5 z0 c: N+ {  d& ]( ~1 [/ ~. t# q' Z1
    7 ?: T/ U, n$ D3 E0 m1 S$ R+ \20 W2 T+ R5 O4 Z. A: C
    34 K$ {& W" N% Z: b. B
    4) G" S! w/ c# z4 y
    50 A4 Y! J) d( Y' m
    68 z2 E1 q$ `% L) R0 z3 U9 i
    7+ N) w% ]9 ?2 H7 r
    86 w* ?& `" E8 P& j
    9, C8 b) i- }! t/ M
    10: ]4 t' y: K% u
    11
    0 ]# X6 \2 H) P120 E8 x, g! z$ l
    13/ l! b# d2 g$ }  C$ R9 I) l& D
    149 @5 C% Y2 {( H/ M+ X8 y% q
    15
    6 V1 t* g& S) d4 ]8 v0 a16- h2 e6 f6 x5 D  N% c
    17
    $ J* m& C& Q( A" t) H; k( k. e7 E这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。; \; }  w; k; u2 r& T
    5 D) @9 W1 j/ x4 Z; e, I
    【练一练】/ S6 t; ?6 [" F5 j
    Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    ' T* i, @3 ?1 Q# H
      j3 r% t5 z3 z. m  w6 G% bls=['2020-01-01','2020-02-20']
    # g6 i' K+ K8 ]% V3 p4 g, J5 Z7 ]def dates(ls,n):
    + l( e4 s$ c+ k: b' a    min=pd.Timestamp(ls[0]).value/10**93 \, T) n+ y& u
        max=pd.Timestamp(ls[1]).value/10**9" ~0 r9 e3 L  V
        times=np.random.randint(min,max+1,n)# h: J( {/ E9 z# g4 ?* C
        return  pd.to_datetime(times,unit='s')
    # M* N, m: p% y9 ^# G8 ndates(ls,10)
    , Q" S3 l9 F' u$ G6 c. q* [: l, a  J5 E4 L7 |7 n9 @
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    * R# q! |8 E% p. u  q* Y               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    - Q1 G( O) c4 B, `               '2020-02-15 00:18:33', '2020-02-11 02:18:07',
    3 J: m2 D& o1 a3 D* W5 g               '2020-01-12 21:48:59', '2020-01-12 00:39:24',0 W) b! ^' b- n6 z
                   '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    ; U5 K  Z" R5 T7 c( U              dtype='datetime64[ns]', freq=None)
    # O4 c: H3 A* m: s4 Y( A6 k1' i' j: o$ d( ]9 o0 y5 `
    2+ o7 g: o; f0 K. J. V9 q
    3
      {8 ?" J5 c/ }" ?& Y4
    ! \3 f( f# c* ~: \: t& b1 D58 v; V$ B9 @' ^8 B
    6" g' L4 A7 m  n. U
    7- c3 m+ _2 g' @, Q- ]
    83 L6 m# d5 y% f+ Q) ]
    99 h( h0 L7 M2 t3 @
    10! E) A. o- |, P: [; N/ z6 P0 H8 o
    11/ z. |! |' W0 k+ c
    12- X' l& z2 B" l9 F2 E
    13
    ( L* u4 z/ z0 X/ }14. D2 Z" d- X2 r4 @: q: w
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
    2 m! i8 ]) G- v2 L6 C/ Is = pd.Series(np.random.rand(5),/ _8 J- A6 p# f7 J7 s( a+ F: R
                index=pd.to_datetime([
    ' M2 q  z( I* ~0 p- T' B# J( C                '2020-1-%d'%i for i in range(1,10,2)]))' h1 ]. W/ c* }4 d) S% `

    ! A/ g6 C, o: X( z- n- p  K: N/ G% p  L, Q. Z! v
    s.head()
    $ v0 E. U$ W# s: {9 O( ^6 H5 FOut[29]:
    5 [1 k, u; t: G3 g& @# P: [9 }2020-01-01    0.8365788 v7 S( {+ b: t6 x. r+ t
    2020-01-03    0.678419, f: I$ B# X6 z/ [
    2020-01-05    0.711897
    8 W1 f0 \, \/ U0 D' K* o6 V2 e2 r7 A! e2020-01-07    0.487429
    , T8 X/ ], z) r2020-01-09    0.604705, n$ b  M& B  R+ k% Q' o" x
    dtype: float64& L5 F' i- d) K% t$ F9 g

    6 T2 `5 h  I8 S% r' z/ |s.asfreq('D').head()! }4 J( ]3 }  d& `7 q& [
    Out[30]:
    - L" H  R2 _% h) ^, k; J2020-01-01    0.8365788 U2 U% m# Z% \* C2 |# ?
    2020-01-02         NaN
    * k; d$ {" E, m2020-01-03    0.678419
    & u* l  \& U& P3 `8 J2020-01-04         NaN, u7 I: ~: W& ^5 [8 G
    2020-01-05    0.7118977 D5 j6 o, i- Z+ P9 T: _$ b
    Freq: D, dtype: float64# U/ q# R: f+ h$ B" ?+ P) v, Y
    8 o: ~" R+ X! v5 t2 @* Y3 z& t: y
    s.asfreq('12H').head()  x) V& [1 l2 G0 N1 V* ~; d
    Out[31]: / b- z' \3 q7 z, R( ^5 I
    2020-01-01 00:00:00    0.836578
    7 ]: c7 e4 g# C# S9 y; a$ _# y2020-01-01 12:00:00         NaN
    & j& R4 g1 o0 ]; S2020-01-02 00:00:00         NaN
    3 l) O9 E# S, c' |0 g: [# t2020-01-02 12:00:00         NaN
    3 D8 L5 Q: g! m, @2020-01-03 00:00:00    0.6784193 ]+ A6 N' x; ^8 p5 D7 p' D6 ]
    Freq: 12H, dtype: float64, e" c/ |$ u$ l1 s
    + e2 k& |) P( t5 `1 W) h2 C
    1
    6 e* p  i) R" m27 H8 |: m* O8 i% [
    3- L5 Y% |1 l; x/ ]1 {! X/ `$ D
    4
    , H! {6 ^. W% Z56 Y8 x6 [5 f+ Z* }
    6' `( t8 |5 ?$ U7 O( J$ s$ a) Y
    7. [, T1 M# i* u  @  x- Q
    8, q+ a( j$ G* o- R% e
    9
    . y7 f* f* M4 ^5 ~8 L0 G( E10
    $ s  H% G' b, s11
    " y8 b3 Q/ C& P2 s" n12
    % y3 W5 ]: ?" [7 c* F& }13
    7 U1 V: Q+ }2 _; {& R/ l142 K8 Q; s' g* h1 j7 M3 {' l3 z
    15" n2 G1 t3 G1 U6 {. }  F
    16
    % `% C. q# |2 W& s) u, S17
    " B0 ~& L& n' b& q' W18  J+ E. W/ _& \5 D" D* F- v( M) v
    199 e$ m9 k- n: B) w' ~8 \4 e( D
    20
    : E$ W4 x- d: \! J2 j216 {# c9 F' @! h% Z
    22
    7 p, \# e% K( ?% z: L% L23
    ) B0 b6 T4 F  |. k) q8 q+ r245 Z% z9 i# Z/ O' Y$ e) t
    25' `$ Y! ]! I0 G6 y
    26' o: U2 c* G" E0 N2 ^! o
    27( S4 C, \( o5 ~7 w% G" u* |, d
    28
    + T; w' i# w2 a. o+ e29
    ) p8 m' ]+ X- J30/ A3 S. {* _! I* l% L* n1 ]) ?
    31( W2 S9 Q5 u; [
    【NOTE】datetime64[ns] 序列的极值与均值
    + v; X) M7 Q, v4 u! D. }0 E, L  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    + W4 l6 k$ @, H4 c% E- r1 x
    ' t3 ~, a) E9 L, G4 D$ T. z10.2.3 dt对象( q$ x8 F7 k3 V( h
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    % j3 E8 z4 A+ C1 n) `* A# Z$ T0 u; M7 k7 ?
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。3 `# c$ a: F7 Q; f6 R
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    / g) Q8 T6 ?5 [! J) N: s$ |9 G3 Q
    0 l5 w( j. ?- s- Bs.dt.date
    0 L0 E/ m! {2 _Out[33]:
    $ K% e0 D8 P# N& I0    2020-01-01
      K& r) \) I; `1    2020-01-02# A* q# I1 o* r7 Y6 y
    2    2020-01-03. A2 Z% h+ T, B$ r6 g7 A
    dtype: object2 g) U2 l$ d/ Q$ B, q

    7 Q5 e4 o: M4 \( j" f# Es.dt.time
    * E2 p: U8 i- K& W& k& u9 iOut[34]: : U6 ^: ^" V% Z
    0    00:00:00! i; @) u0 [0 v5 \: }* h2 f
    1    00:00:00
    1 W. E+ Z9 J2 B; p. Y2    00:00:000 E% C: n7 C9 h" i) J
    dtype: object" o0 o" U; F( Y3 g  G

    3 a- v9 v. C9 p7 Cs.dt.day
    $ f$ @+ F8 d. y7 S' {5 i. MOut[35]: 7 X" _( T+ ^* q( K+ J1 E
    0    1
      o# E2 _. p, ~# q  u3 M  i$ s* Z8 F1    2
    8 ]8 L0 \- a0 F6 V) K3 g# a) E2    3
    / Q! `; Q9 P% E/ v+ }5 i$ V; kdtype: int64
      V$ _9 E; t2 D: _! B; f3 _+ G! ?. D% G$ K$ v, e; l, K
    s.dt.daysinmonth
    " T3 p9 a; t( S5 f+ A; A5 k( Y2 cOut[36]: 3 r% u" _1 @8 _
    0    31
    5 A" k) {1 D- G& ^4 o1    31) Q' r' A/ a: |* ~9 H
    2    31
    2 m! p/ F/ M4 A# Y2 h5 Pdtype: int640 A/ q. E1 P! y8 G
    / t0 M6 l+ {* l6 ~6 a, k( e
    18 [2 B- r% p( U& U) P: u" d9 e& Y
    2
    % Q! Q0 M: r2 v2 Z3
    " u3 p1 e* O: ]7 g1 k. P/ p4
    1 h. d6 I  w2 ^2 s  X" K3 N5) G; t7 [9 c* w+ j3 a, F
    6
    : g: Z8 F' b& b7$ t' b2 |5 \# @: m  P
    8% w" z5 ~  \9 a3 }* N+ E
    99 n: e$ g% G: E* T% T
    10: g: \! s- O! p- X, `
    117 f9 ^0 l1 \. s3 |. z1 P* D3 a# i
    120 y0 i! C$ y& h% i' v- x: X
    13
    ' ~$ y1 J0 c# A1 n14
    7 q. l$ |. m; @) I# k0 B* Y15
    / ]' t, `: l* d  S- `16
    $ X1 M' O( E3 C- X( }179 _4 X4 R+ B7 i9 ^
    18. H: U! ?( a& j, t' i- M
    19' f, Q9 A# V% y. B
    20
    1 U# |& s* }) B& H21
    ' k- q0 f8 F0 j$ C( O% Z22
    3 a; J8 V  V' U4 q9 o5 {6 ^4 r23
    # T* d! f7 m( U3 G. s245 a# d) ]3 L% x4 o* K
    25
    $ L( Z; i, ~  P4 C26
    7 W( K2 W. C, D27
    7 O, a' a9 d; n& F6 }28+ n8 a0 ?" \) g+ G4 R4 c2 Z
    29
    1 L, h3 `1 @$ `' r8 Y  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
    % _& u4 f: i9 j/ i2 S1 q
    1 _9 V* \+ F! x1 rs.dt.dayofweek+ I3 O1 V  q/ r- x
    Out[37]: 6 l% ]+ Y) o' P, o, p
    0    22 ]; Q' L" ~) `& `5 e9 f
    1    3
    & |! R2 s5 {4 r* f( k0 ^: |5 R6 {2    44 U, a; X& e. a) G/ u7 f
    dtype: int643 f5 O3 m' L. j% Y
    6 a( v. c3 B- l. X. o- C1 F0 R
    s.dt.month_name()( N5 E. P6 `  [
    Out[38]:
    ; k8 a$ e. f8 P; U0    January2 f' h& f! B0 {1 X1 L
    1    January5 c) j: E& K- r, P) p
    2    January+ J& e; [4 Y5 d  I$ K
    dtype: object
    # Z" |4 e8 ?. _+ G5 R
    ; [% H6 v  C4 L, H% Qs.dt.day_name()
    1 F' ~7 X0 |) h: m' t' }Out[39]:
    0 i4 N# ~4 _# [3 o8 ]* m% o+ Q  x0    Wednesday/ p8 R8 D) f, j$ K9 ^
    1     Thursday( j" p! t# r- q4 A
    2       Friday
    ' z8 l/ ~8 h* f0 Mdtype: object
    / P* Y4 l+ v! Y  S: \- l: X
    3 A5 B- a+ g8 }7 W) _- Q19 ~3 S1 @8 U0 N% k+ a: s' I$ u
    2
    - ^, k! E9 F2 U3
    0 ]5 `1 b) d" t0 k48 ~& T- ?2 z, {" M7 O% V/ C
    5" Q: y8 a) R  Z# L3 u% M/ m7 H
    6
    6 }5 ~" E- A! A$ {# F7
    8 o1 k! t. W, K; Q4 }& s- B( |8
    & b; X/ g4 }3 K8 j5 M. E8 o+ }9' H, R0 l# m1 Y, \2 `
    102 E6 _% k+ r9 N. c8 `( V- K
    11
    - b7 L2 j4 A/ y2 y6 `$ K129 @2 Y+ m% g& R- `
    13/ U# O( Z8 G1 J+ Y' n* t
    149 M. u* ]/ @8 R0 `
    15. T; H& S: ]& B. w6 N9 Q0 A
    16) ^* P/ L  Q5 v$ K/ Z8 W  ]/ g# r; z
    17+ h% }" m9 N  Y  R; U1 y7 Q
    18
    3 }9 s! K: ^9 {/ K; H; P19  x, @, g- b' J' C
    20
    , K8 r8 d2 A, n7 C% ?第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:8 b3 e, H! V. C/ V: e0 n/ n! @
    s.dt.is_year_start # 还可选 is_quarter/month_start
    0 x, P# @# h; g& h& f1 MOut[40]:
    4 J- t, m: E; u0     True5 m0 N" N/ n" `; t( f! |: S0 {- X8 X  Z
    1    False
    ) [$ v3 T4 M# s8 V2    False; @- S. ?/ j% M% n4 Z& P( z* {
    dtype: bool
    2 R! O3 Q1 C5 a% \3 h. d" P
    - W2 {8 U! U7 J" _s.dt.is_year_end # 还可选 is_quarter/month_end
    ) M7 |' N% @1 Q" `  @: W/ Y- U/ c- DOut[41]:
    : u/ P* e& H  R7 W/ P) q9 {# Q1 h0    False
    : D& S$ m4 Y6 y% A" v1    False
    ; k( t/ u7 @* u+ \4 Y# A3 d2    False8 V4 a" V5 T) U3 }
    dtype: bool1 a: t; v! b4 s% K
    1; h) C2 l) W7 y" P+ F
    2* ~3 B9 i+ t) _' h$ D
    3
      S  \5 }2 A) |& E/ K8 G4
    5 X; O& C  W" P/ D' x' D( K5$ u0 p' ~2 v( y' p
    6
    ) v& R2 ?: u: @75 {! D$ P; W9 s4 F: m
    8. B; l$ `4 H' M  R) U
    9
    $ @9 C# \. D* Y" q; a7 ~1 Y5 E$ J10+ u( G( L0 V6 S0 n8 u
    11
    : U  b" H8 t& ~1 V125 U; w9 m- r8 J0 q7 q+ b1 {! o
    13
    7 e8 h0 M6 ~' I* Q  u第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    . H* v7 F& m! Fs = pd.Series(pd.date_range('2020-1-1 20:35:00',2 s7 [* U; c. i  |, `& q5 d& g7 y
                                '2020-1-1 22:35:00',
    . v# q4 m1 j) V* M* V                            freq='45min'))
    : u" S& R5 [7 d' S: |7 L7 Y
    & Z  _- ]+ r" w) t3 t/ @2 P0 _
    % x* j( i9 a3 T8 i) As
    8 y4 p0 R; Y6 j6 uOut[43]: $ D" _% o$ _& N6 D  r
    0   2020-01-01 20:35:004 B2 e6 b- X7 B: `' K7 H, n" J$ q
    1   2020-01-01 21:20:00; A+ S2 X; T" A( P% t
    2   2020-01-01 22:05:00: Q+ R% D5 }# f% |9 ^8 p
    dtype: datetime64[ns]# L, z8 m( d% \: @/ L

    % N# Y# N& k2 ]2 C% {5 ps.dt.round('1H')0 r2 V0 }. ]( O4 u; w1 q- U
    Out[44]:
    . O6 ^7 ~7 Q1 T6 n, d6 q0   2020-01-01 21:00:00
    ( y- S2 N( m, B) @- v1 T1   2020-01-01 21:00:00- x6 J% [2 s6 t2 X  w9 d
    2   2020-01-01 22:00:00! M: m  `! j  W$ x
    dtype: datetime64[ns]. {# |! X$ S( _. T" J2 R- M% t. Y/ M, p
    $ ^% F# ?9 F* t2 k) `
    s.dt.ceil('1H')
    4 L0 x+ F0 g+ [- w' d  sOut[45]:
    , N2 E( w% L  s4 d0   2020-01-01 21:00:00
    $ I7 O& a* H) m8 [  D6 U6 ^2 g! A1   2020-01-01 22:00:009 |" m1 h3 T& s  T0 N
    2   2020-01-01 23:00:00
    : y1 a! v9 k- J: Z% gdtype: datetime64[ns], V0 ?) p* l/ R- M/ ?

    # W- V" {% M6 xs.dt.floor('1H')! z* W, q7 U& v- @  f6 T3 k
    Out[46]:
      f, {! u# T$ ?  k0   2020-01-01 20:00:00
    * R3 ]* j2 m7 ~# [# H1   2020-01-01 21:00:00; U) _6 d9 W) \# V
    2   2020-01-01 22:00:00
    / y# I  I9 F: z" }3 Q! k8 g" \" mdtype: datetime64[ns]! I1 h' H# V" T  W% ?. i, q
    ! A+ I* `! k% m* w+ B
    1
    $ W6 j8 X& n. c& E% ~2. e+ R; a6 w; m
    3
    " s' m" L+ L$ p; x1 U) V4, g6 B- o% s2 A
    5+ i  z6 q' s9 H
    68 S' P. H. u6 i5 l& J
    7
    " ^6 s$ N8 Q" i( n/ [( e8 Y1 F! d- n8
    8 f3 c/ e6 M5 N; d9
    ; a6 ~7 l: o3 m5 W! v1 U- o" ?10
    ; h# k" t6 E1 T; P* o11
    3 S; w( E" Y% h3 P3 t( \12) w* u- m4 k: @" Q/ Q
    131 K% o; Y- N# j& C- v7 Y
    148 s0 B7 r. [" i0 p+ ^* C- p$ L
    15
    4 K6 {; I2 e2 V1 c. a' [16* u! a. I' e0 E! i" _, p
    17
    ( N: h2 y1 a8 o3 \187 R1 A+ C! ^: l" o, o( P4 ]
    196 Z4 W' D4 W1 j) w
    200 d, Y; s' m# r
    21( E9 H/ \' y1 t
    22& @8 }; @" X" s+ x8 `# E# \# d, _  u% R
    23
    . |& R( \3 w, \+ Z( T245 g  I; f' i8 [5 K
    25
    ) s% U' F; F; m+ A26. J( E( T3 ?2 P5 e2 n- `
    27* A" c* M. j% j; Y4 q% ?* e' q2 ~
    28* K6 v: M- Y' e* {2 Y7 }( R
    29
    1 _6 ~( F) u: `7 B2 [3 w+ _- Y30
    . W: s  J8 W' C% y31$ m4 c6 ?& |7 b$ I- a% \# q
    32
    ' |& k" [8 m5 c5 V! L10.2.4 时间戳的切片与索引4 O7 i$ Z; K4 X6 @
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:( d+ f- }% t7 Q  w; D

    * j2 a: d2 E0 U" @5 F7 C- j0 j利用dt对象和布尔条件联合使用
    $ A* Q* D. y3 ?: O( i0 [利用切片,后者常用于连续时间戳。
      H3 N1 w! T- |# M# {s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    & g( C0 O( e7 G) ]5 aidx = pd.Series(s.index).dt
    9 t% y  \, S2 Y' f  \1 @. O( zs.head()! P1 d* h% K- s$ i% H
    & m' |; ]! C. b" R. i; X
    2020-01-01    0
    8 J# V4 W- i. {8 s' `' \2020-01-02    1; y" ]% |1 r$ [* W, }- o
    2020-01-03    1
    : V, D  q# B0 [' c/ m% U3 t2020-01-04    0
    5 u# n3 @5 A" ?; w6 b5 h1 A$ L2020-01-05    0
    3 d% Z6 `, X6 i" b! BFreq: D, dtype: int32
    8 k% b$ x0 _- z; Z3 Y14 t% F  q$ k- \
    21 {; T  {+ P1 s2 y
    3
    % e  r/ O" S( W( O+ M2 l4& a% J( g# `! ^5 Y4 x! ]- H
    5
    ; m9 m% ^+ K6 L4 U6 v: j9 U6% x+ d' S, F9 Z# T, _, R
    7
    " |) t! F+ ?- Y" c# \5 X: Q& l5 k8. C* k) S( ]( `9 l" y, ]; U% v9 W
    9# D" k% @  Y' ?
    104 y5 m6 v9 U; M, d
    Example1:每月的第一天或者最后一天" c5 f6 @4 _3 z; S, t4 g
    ! {+ G( [: p( A  O5 V) R
    s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values+ }+ g5 l  M$ q
    Out[50]:
    . ~" j/ ^; K/ e) n. j2020-01-01    1
    2 a7 F7 T: C& Q7 e2020-01-31    08 a2 p: A, \% a4 \
    2020-02-01    1
    % ?1 }  i4 T7 r- \" R- W5 ], d7 s2020-02-29    1
    8 H% c5 z: ~8 D  b1 I" r2020-03-01    0
    5 J, O+ \1 T. c  ldtype: int32+ k4 H' a4 u4 a0 m8 ]) u
    1: o5 X; Q. }  A3 Y
    2! D- `9 v# V! x) h! V- `- @
    3( x* W6 O- n1 h2 `; K$ W7 Q5 `
    4! w" C* C9 g4 J  _  m! g
    5* F; N7 Z4 g# X# N! T" K
    6
    4 S1 Y1 {: o$ p1 C7
    ( F4 K* j# S3 x2 N. ^8; Y5 M, K6 e5 A, D6 F
    Example2:双休日) h$ n; O+ r' X# l/ S

    % l3 A6 |3 L: D! ~% i  Y! D8 ?& M# ds[idx.dayofweek.isin([5,6]).values].head()! V% {% _: x2 x2 T2 _( R! P
    Out[51]: : Y% K4 B" W: p& b
    2020-01-04    1
    4 _- D1 }' Y" l  O" L8 P) ^; N2020-01-05    08 C1 K0 }. L5 K0 q
    2020-01-11    09 c" J5 n. |" p. r  n
    2020-01-12    1
    * D5 Q: ]& s) I/ I0 x2020-01-18    1
    ! X4 \& V7 @+ }- _! C: h0 l; Jdtype: int32/ I$ R& J- M/ q  z; B
    19 [4 e  g. _% C7 f9 o- k/ t& O  s. m
    2/ x. }1 a: y9 N! t
    30 j3 Y6 c( s4 Z( P
    4# H" Q8 N: _4 W" U% S6 ?
    55 u) }2 j' S8 ?- ~* P: n) Z# _
    6
    7 I. t4 a' s/ _, J) R& s' L5 b# {7
    5 G0 |0 i. A4 P. A1 ?8
    & e3 O' b7 I) r" n5 z) K9 C7 K" n4 aExample3:取出单日值
    " z" K) x; s  w+ x  F- F1 e7 K' _0 _
    0 N; b7 a0 U1 Q8 J# t8 k' Ws['2020-01-01']  X+ V  F, a& ^8 w) D
    Out[52]: 1
    : u, o- u5 d# t/ j& R9 @4 W
    - |: p. i$ l7 }9 Hs['20200101'] # 自动转换标准格式
    * u' w& a6 W9 O( v+ g+ yOut[53]: 14 s5 X; A: p9 a
    1; M1 p6 j9 U& L- U$ s# |
    2
    ( B% I! P3 N0 I' w3
    - P5 B1 k" D4 @3 j( ^+ Y4
    ' I9 L" Y* H9 Z# R0 p& c+ T5
    ( h% n' f2 I1 e" W5 H- |Example4:取出七月
    4 s0 q; S, B8 R4 X0 J1 [0 V  |8 t$ G6 t
    s['2020-07'].head()
    8 C0 D! m7 a- q5 i% x9 r; X. lOut[54]:
    0 R% O. Q( r% m- Y2020-07-01    0
    ( g/ V- J* Q4 O0 H3 U+ h2020-07-02    1
    : [( x' T4 Q* ?( M, a7 `) ]2020-07-03    06 s6 L7 S1 S) n! F: k: ]
    2020-07-04    0. @/ X, R" \* [
    2020-07-05    0  c1 `  P2 a, d3 v  w
    Freq: D, dtype: int32
    1 m: a, A# P' K) B, c1; K* `  n, I5 \5 U! U( P
    2
    ( G% m+ P* H5 k$ Z6 ~! f3 z3
    5 k: u4 m! q: G' j! G- b. U4* S, h4 |& J: i  v* i, }
    59 L3 [; X3 H3 B. U) H$ K
    67 l( O0 W  x! v6 K( q# l8 A% a
    76 _2 j# @6 c2 K+ H
    8
    * D9 m0 v2 B" J  Z% uExample5:取出5月初至7月15日0 p$ z% X; O, u3 ~0 x& Q% ?
    2 n. N; Q  q; b. f- H8 K: V+ r3 j
    s['2020-05':'2020-7-15'].head()- j8 T: L$ \+ z9 R5 d9 T
    Out[55]:
    ; s8 Y7 y/ _( \& K* V& G9 d6 F8 r) s5 x2020-05-01    08 G4 z; l1 ]8 n1 V- n
    2020-05-02    1& T( b9 Z/ ~6 y# D' r* t3 w/ H
    2020-05-03    0
    + j" J& L/ \' t2 x1 R9 y5 ?2020-05-04    1
    " A% q' O- ~, ]- ]2 v2020-05-05    14 ~* Q- ]2 j$ Z* E. \! j/ u
    Freq: D, dtype: int328 Y  W1 \9 @, p- J! R
    & ?5 k0 F( y9 ]' n2 H8 ?
    s['2020-05':'2020-7-15'].tail()
    6 D7 E5 a# C7 y1 G. E* V+ fOut[56]: ) F# c- K" Y$ Z, k8 D
    2020-07-11    0' U9 [% l& ]/ Y/ C6 c1 G0 Z. X( `
    2020-07-12    03 X+ g* ?' b2 Q. W" d0 E# R
    2020-07-13    1
    ) j- R2 G' I0 j% v7 w* @2020-07-14    0" @3 U8 x0 b& i/ n. w! H
    2020-07-15    12 |9 M) f1 O! ?
    Freq: D, dtype: int32
    - |2 |% q" w/ A/ \
    3 c/ U# h% s0 Q4 v15 n' W/ h- a  G/ T  \+ J6 f
    2
    / d( w# C9 K6 y9 C3 o$ \3+ h1 g+ }! J: ~# z, w5 P
    46 p3 _4 e+ [* |, f8 h
    5
    " J/ D% ?& z+ i6
    $ J% Z# m9 P# w' z7
    : W. P  m9 ^) I0 x83 J5 @. U) _% P) [" X
    9
    1 j0 K6 E  C3 \10
    : b5 f8 B# U9 D" @3 u11
    % \7 h1 H) ]  I- G; t2 L6 e! @12
    " P' F5 d( _5 p/ G13
    ! J7 f5 p9 g2 C7 T14( L! e+ X  i( E9 z4 X- f/ M3 z
    15. l3 K" @3 R1 V& K- c
    16& R; S0 G1 z! ]8 l
    171 P. `, X8 s1 ?+ w
    10.3 时间差4 Z* d$ d1 P  c
    10.3.1 Timedelta的生成* C! y% X8 K) t) ]
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)
    ; x6 ^- w. }6 Y) s6 `, I  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。; s$ @; p& y* N
      可能的值有:+ N. q, `# Q7 H

    7 F9 }% }6 D. j: I' q+ \, i‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    - W) l0 o2 K' m) {$ a! D‘days’ or ‘day’& G& n5 {( @4 B
    ‘hours’, ‘hour’, ‘hr’, or ‘h’
    ! A( m. X3 I% j9 ^. H  ~‘minutes’, ‘minute’, ‘min’, or ‘m’
    ( k  \& o! i0 [& B% N3 o; u+ m‘seconds’, ‘second’, or ‘sec’* x. F4 n* f2 _
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’/ ~' V, D( Y8 N% T
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’; o: z: ~# c; y/ w
    纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    8 g  _- D3 @& r0 O" W- _+ o" D时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    4 I5 ~9 W& F0 ?0 y1 w* ]# Vpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    + o, a+ k) O0 C3 D# c0 ]Out[57]: Timedelta('1 days 00:25:00')' P6 e' B3 a# o: O6 T0 w

    3 F0 q+ J# ~3 j! V" M8 hpd.Timedelta(days=1, minutes=25) # 需要注意加s
    % m- J) Z! }+ pOut[58]: Timedelta('1 days 00:25:00')
    3 W- Z0 H) Q  N  Y3 S7 s- Y9 X3 S5 g
    pd.Timedelta('1 days 25 minutes') # 字符串生成
    7 k# l# U* J' a) I) E3 ]6 XOut[59]: Timedelta('1 days 00:25:00')
    : M  ~( c/ S/ d
    4 Z# p! N7 t9 ~( z  f2 R% L! }pd.Timedelta(1, "d")
    3 k9 a6 h7 u& ?Out[58]: Timedelta('1 days 00:00:00')
    + A* `0 k% P: Z- q11 a8 e/ F' k) S5 `  X# f
    2
    * j! P# o' m! C3
    7 S4 z( [! L7 \2 k$ V" \5 y$ @4
    9 {0 A9 L3 b" S4 X& m: M5
    ! U1 T5 b/ [- |1 N9 e9 k6
    / g" S3 ?7 x; }8 I! \" ~1 `. l7
    & ^$ g. |; i  ^2 M8  W% A* F2 S% A* a" Y7 ~  Y) D
    9
    . t9 r' O" N0 @10
    6 X8 v* }  P/ R7 |0 s  W11: {7 ]. y; P* x
    生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :' u, O4 W7 h; z5 k- C
    s = pd.to_timedelta(df.Time_Record)4 W1 [7 e, P3 C/ J' A2 b! b
    ' a2 ?# W7 `2 v- s/ h
    s.head()
    " c* X! ?! ]+ c$ h' Z, |, nOut[61]:   f1 `0 g  [( K6 P& e* m) t
    0   0 days 00:04:34
    $ @" K5 c. k4 n: n1   0 days 00:04:20$ g9 d+ o+ ~+ N& e" g
    2   0 days 00:05:229 V0 X, S1 l% w  y9 O' r
    3   0 days 00:04:08
    " W% K7 a) y6 D4   0 days 00:05:22
      J( m# `# v/ B* J7 u% c3 `Name: Time_Record, dtype: timedelta64[ns]# I- b2 I6 ?" I# x( Z) p2 w* Z. }
    1, `! I- ?  q, M* l
    28 A# Q$ ^4 R; N5 V. ?* _
    3$ A6 B% a+ s3 Q# f8 e7 H5 @
    4
    % r/ N+ P; ]! n0 M* Q1 n; N0 O" E7 u5" X9 P- Q7 y: d. [. @
    6
    $ M2 M$ _' l5 c& s" u$ Y1 d3 Z' E4 t7$ \  w; N( h, V* M3 u1 o
    87 `% V$ {- [# l
    9, F! @" J# q3 v* r
    10
    * O# |3 g# J" E  Y3 P, f7 d7 F与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    ; Y; _7 i4 X3 Y$ R7 ?pd.timedelta_range('0s', '1000s', freq='6min')
    ) }# Q' @- h2 b& C2 _8 d! r1 F# S. Q4 OOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    6 e' j/ l+ D. N0 X9 ~# t2 S+ X2 O# g& o1 j1 Q; P
    pd.timedelta_range('0s', '1000s', periods=3)7 g$ h$ w$ W; F4 o6 J
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    . r" e( U/ T8 p4 G  m  ?1$ G7 v8 j: p- g' q* P
    2
    . L% i6 \5 h" K* m5 G6 i3
    , x% U2 k! p3 f. T& l/ _4# `0 [& t4 q- T! Z- n
    5
    , x2 L, y: Y5 O对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    9 i8 d4 n- T& @) T+ u, ws.dt.seconds.head()- y) I+ [) x: g' h1 N% g2 m
    Out[64]: # ^+ T5 P- o& w8 ~' d
    0    2746 P& ~& l: B- }. l! }
    1    260
    ) A1 B4 i5 K0 R9 y4 A" a2    322
    ; h+ {" |* r6 x4 G5 r3    2484 B: _* N- Z  L+ g
    4    3228 |" w, t9 q* O& G" t
    Name: Time_Record, dtype: int648 k# ]# g( s4 O6 F8 t
    1
    ' S& F1 x2 l" M2 e  c2" v2 l) B9 r( Q" M. P% x
    3: r5 ^/ N8 a, a: M, }. ^) R- x
    4
    & R  g% k& f0 @0 V5! {3 n! C" q1 Z9 f3 B: M$ q
    6# ^- x5 ~8 \9 d! X* S% T
    7
    ; t7 r5 ~( E3 o& z: A: R8 {8. U: `, g0 H8 J$ @
    如果不想对天数取余而直接对应秒数,可以使用total_seconds8 T# ^# v3 S/ H3 b8 s+ w6 z! A/ P
    % w: \7 w: V6 T2 H
    s.dt.total_seconds().head()% |. C) b$ K( d2 u9 I
    Out[65]: $ T# M9 S1 C9 R: R
    0    274.0
    . b+ f5 E% C( u+ B1    260.0
    ' ^2 W7 R5 }7 n& Q* E/ X2    322.0
    1 ^: Q+ f5 N, o- g3    248.0+ j' f( q0 n; L4 z
    4    322.0% c' x4 C8 O: q# J$ L
    Name: Time_Record, dtype: float64& J  z% Q( R5 l, A% D1 ~
    1# W+ D* F9 N+ l( s2 P0 g: ^! M" N
    2% I# ~2 J. B: X. X: q; b
    3+ [1 e- H' J8 q3 E; k6 S/ x, [
    4
    & h& h! a* _) T1 ^' S# r5
    $ [' L7 a8 z# H  T$ s6
    8 o2 }, d2 A$ T2 s7
    8 L4 K- n) m3 g$ U- f8
    ) _8 Z7 I  P) p0 U+ a与时间戳序列类似,取整函数也是可以在dt对象上使用的:- j( z: i0 y0 k$ u
    & @0 y% A- w  l( c
    pd.to_timedelta(df.Time_Record).dt.round('min').head()+ [4 T( c7 T3 A1 A9 b
    Out[66]:
    9 y0 r8 m8 |  u; U0   0 days 00:05:00
    1 Z$ ?4 }* ~- r7 b8 V: h0 C  ?7 o; J1   0 days 00:04:008 i7 c' P4 q0 Q1 `3 _" R
    2   0 days 00:05:00
    8 N7 U  w/ Z" O; x3   0 days 00:04:00
      G* m0 C3 K3 D9 Q) M4   0 days 00:05:009 {. h- }" v( {# B
    Name: Time_Record, dtype: timedelta64[ns]
    . e7 q' k# d% ~# W. f1& t3 D# X8 A# H( t7 C
    2
    1 K, u: }( C# m' A- F7 |3
    " ~  {8 k8 X. Y" @! H$ n# f4
    6 Q- x1 n: \7 d" T; l5" K$ w/ b9 O; @9 z
    66 Q, H# C% S2 f1 D2 k# t
    74 \2 G( y0 C) x; C1 w% n
    8
    ( S# Y# n4 E" @2 L" j) |10.2.2 Timedelta的运算
    # I; R- p- n# c( b0 g- j% V单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    ; ?. Y! H. k! X) |* w9 A4 xtd1 = pd.Timedelta(days=1)
    / D8 z8 {( H9 y$ a, \td2 = pd.Timedelta(days=3): s( u" \( k' ?5 K/ P0 u6 p; C2 j
    ts = pd.Timestamp('20200101')
    ; \' T" b% v5 C
    0 Q' `! Y6 n  S7 vtd1 * 22 a# W: F8 I& o' S% F1 v6 y5 P
    Out[70]: Timedelta('2 days 00:00:00')
    9 V: C. I" B* T, B: L# z6 [: a
    9 a3 _+ E& }) d* ?$ L& K( rtd2 - td1$ ^& w, f* q; ^- }2 d
    Out[71]: Timedelta('2 days 00:00:00'), P; D$ D8 W4 G$ `
    ) J5 k) T/ D4 M. S! i
    ts + td11 S: J( P$ b( [) j% P
    Out[72]: Timestamp('2020-01-02 00:00:00')( H; L3 y; o7 w  A) j/ ]8 b) b

    $ q5 B) G' ]' M. w. ^9 Rts - td19 j/ I, Q- q6 H" _+ Q
    Out[73]: Timestamp('2019-12-31 00:00:00')
    # W" m- N3 G3 [3 m- X$ K6 s4 }1% ?" C1 ?' u9 Z" p
    2
    0 T3 N8 G1 |7 D4 E3
    & C. `! r( i& S6 E' z) j4! j8 u2 B" ?1 _, j. j9 t& _
    55 z7 U9 A6 g' o0 P
    6
    4 r/ N) D1 k. w# O; U0 r7
    9 v6 v  r, ?) i5 Z9 v6 X# K: t3 Q8! t( P' S; p6 c7 f( J5 |. W
    9
    / O* L) y8 {  \5 b4 l: m' f10
    1 A) u7 s( W, p9 ?, U11
    ( K- a6 k4 {0 ]" i3 k" d* T1 j12* B- ~; `6 s; ?8 g2 i$ c, \$ X) o
    13
    ! Q; n& p! ?; q' {. [14
    + `& D6 b; J  _6 f: p) ^7 o15- X/ P! p5 |9 T6 Q. _( V& K
    时间差的序列的运算,和上面方法相同:
    ' c5 s( q, g5 Z# h6 ^td1 = pd.timedelta_range(start='1 days', periods=5)% ~$ Z( J2 ]3 N1 R3 d( P
    td2 = pd.timedelta_range(start='12 hours',
    ! O! [$ d6 u7 H                         freq='2H',
    6 `# t* S* s/ e6 U! v! M4 e# \/ T                         periods=5)' o1 _, d" o1 \2 U5 T' L
    ts = pd.date_range('20200101', '20200105')$ }+ A0 H5 B! s
    td1,td2,ts+ N* I' \6 w# ~/ G, L$ J2 u" {
    9 l- A8 `6 p+ s9 ~6 p& \
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')9 |  i+ V6 H: ^3 F8 o1 [
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',) }; p/ n6 f: d
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
    0 p- z7 I6 u" r6 r: [, G. c. eDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    ' z' Q' R/ t) ?3 M               '2020-01-05'],/ H! u/ m6 R8 s
                  dtype='datetime64[ns]', freq='D')7 q: f8 ]8 W. k# g& F% I
    1+ G8 k& }% a( [5 c$ k4 s
    2( _% g9 [5 ^, X: M% q
    30 R( o" T1 E9 b7 s$ I0 V" x
    4
    ( q$ S& H6 y! `7 Z7 Z! h54 y# R) Y' p. t9 `2 |: g+ v9 ~
    6# y# O& m) l8 D. O7 P/ G: Y
    7
    ! m: @9 o$ z# X1 Y: a$ C0 R87 t+ ^) @" x1 w$ _
    9
    3 h9 Z/ M" B7 B5 y  N  V8 C7 U- C- t10
    8 v. y3 q1 Y+ s* s. h113 t: h& ?! `3 q, H
    12! b* [. B1 v4 M. p. X
    13! F/ R' R: u( N1 g8 Y) b( @. v
    td1 * 5  p7 `  r9 G5 [
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    3 l# Q, J5 ^& r0 @( y
    * F* A6 f5 ^% P  ]: i* L1 itd1 * pd.Series(list(range(5))) # 逐个相乘
    ( O7 A3 Y, `: S1 ~2 sOut[78]: & l3 b4 D7 p5 W6 ]7 c; k
    0    0 days
    + U- X, G7 V9 i! j! E7 x; C1    2 days5 c* u0 r4 t7 o% J, A8 i
    2    6 days7 W% m2 v# B- z2 x! q- ]
    3   12 days6 P2 C1 Q% d. t0 ^
    4   20 days
      C# X' ]/ J! F0 \dtype: timedelta64[ns]
    $ x5 U5 ?+ q! Q, o
    % |% U8 E% i  r) K' I: E! C6 ]td1 - td2
    / `; K; X2 L3 v: A3 g0 T4 c6 P! FOut[79]: & G* V  @% {( Q) [% Z/ `
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',2 Q2 G& d" E9 o+ \
                    '3 days 06:00:00', '4 days 04:00:00'],
    , z) q  }0 D* Y* e               dtype='timedelta64[ns]', freq=None)1 M: ~) W: X# l6 G0 U7 q

    6 c: k4 [+ k) Y% r2 ptd1 + pd.Timestamp('20200101')! z1 H) G) Q7 j- Y
    Out[80]: / u& M6 l1 m, z- c
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',1 P( O  O# v8 o0 }
                   '2020-01-06'],dtype='datetime64[ns]', freq='D')
    9 G2 ]  ^; T2 s8 i0 _3 K% T$ g1 T* [8 C9 y* a
    td1 + ts # 逐个相加
    $ h2 n2 v2 G6 Y1 v' j% G, b* k; B' dOut[81]:
    - O" q8 }- K3 n& w" O" s7 r) dDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
    ! y- ?2 z, O# R2 X0 V7 g               '2020-01-10'],
    ) ?7 k% U2 a: ~! Z9 n2 M4 H. U) ?              dtype='datetime64[ns]', freq=None)
    ' J! p# Z0 ]! g% A/ e, N* d6 M( d" N. S$ P; [$ k! k
    10 o/ S. n2 G* U. H: L
    2
    ; i( W, _' D) N. b- \6 T3
    " }6 j! G5 N3 ?( g7 x9 N7 x4: m$ z% i. E( ]( d3 s
    5
      i. O& k2 A7 {$ I) u+ |$ `) V6  d" f% w) g9 ?! ^; \+ |1 L
    7/ _# G3 S+ U9 ~
    8
    . }( x) n# H0 q) Q+ [9
    - k9 f/ b2 s" c4 u4 ~# G# Z4 O10
    # u0 H+ Z/ {/ @1 k- N4 g6 L6 V# h4 T11, B3 J7 Q- \. H* h4 t
    12
    8 r! y% ?1 r" l# {! R" g13
    ; b1 d) E2 W1 Y! l: q" n1 t14
    % S& |$ O  _; l  l15
    6 f2 q& j$ W9 `: J1 X8 q! X2 ~16
      E. x; F# n6 f- I0 F! U17. o6 w) Q0 V$ w
    18, G5 c5 ]& o1 G
    198 h. Q. c( Q8 Q7 @
    204 E( ~6 A2 s5 j/ x
    21
    : X6 `0 \; d+ y221 h4 h0 w' @6 T% b0 i; g
    23
    " V( J0 Z& T% x$ c7 L8 a24
    ' w2 k& `; U1 h$ J251 O- A' Q" L+ _! L0 X1 v
    26' g' o* Q& j0 y8 O7 W
    27$ Z0 p, ^' X) }8 V+ f, |! b% I
    28( f# s1 a( Y4 s. p6 d& t/ d
    10.4 日期偏置
    : g6 z% k- O! v/ F10.4.1 Offset对象- g! A0 v; D$ ~
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。+ h' J" t, D/ H  p5 k" o

    ; J, p& f* Z  r# O) J; GDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:( L( G* B3 y: F4 U# ?

    # ?9 r9 s. _" T9 [6 Z* ds.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本1 W- Z2 `+ G+ m
    s.kwds:{‘week’: 0, ‘weekday’: 0}
    . Y3 b* \: A3 Hs.wek/s.weekday:顾名思义* ?2 B; G# {0 R* L
    有14个方法,包括:
    0 w: ^" q. }5 e  q. R3 ~
    6 z- F, u, d2 X) aDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    2 o3 e- z+ _1 G4 Ypandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。  y( c+ P4 V% q  r* v$ T9 E
    5 F' Z, `# T! C( n) W0 f
    有两个参数:) W: B/ [0 V* l% K7 }& b
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。" s2 t+ W% v+ ]4 O  F2 g5 L7 l
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    2 |' T6 U; \% I, cpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    " Z6 J1 X# C3 ~" |4 f
    3 G: @! I9 Z. [' O$ a& Kpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)  J0 F4 d& F; ?3 [, x: |- t
    Out[82]: Timestamp('2020-09-07 00:00:00')0 f- X: d! J% J- J
    , `% K: _  O* n) q1 @  s$ h
    pd.Timestamp('20200907') + pd.offsets.BDay(30)' l/ a  A+ x" M' f- F7 c# m+ I. O
    Out[83]: Timestamp('2020-10-19 00:00:00')
    1 u, P5 o7 @9 F! O1
    * X4 `! u! o/ o3 E21 p4 @- K; z7 R( b  u( d
    30 j6 F0 F1 a  P
    4
    $ n& Q: b  x$ W% s1 r( v0 C, ?5
    2 ?$ i1 K/ G" x4 V9 f1 j9 s  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:3 _( A  }- d2 h. X6 O! t
    $ N0 v& S6 }" n5 n* x" ?7 D: v
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    4 r+ @8 ?( {' P3 s9 {$ |Out[84]: Timestamp('2020-08-03 00:00:00')
    2 ?: p- A, Z1 i9 U/ @7 N' ]. m. J
    % U; g7 h/ ~- s: |0 apd.Timestamp('20200907') - pd.offsets.BDay(30)" H* L5 G/ P' z2 C
    Out[85]: Timestamp('2020-07-27 00:00:00')" T8 ~" o1 x; y* L
    * ~# S" K) W, b5 j# `+ {8 i
    pd.Timestamp('20200907') + pd.offsets.MonthEnd(), H& ]/ T$ Y. y, p2 U5 y' I  B+ A8 [
    Out[86]: Timestamp('2020-09-30 00:00:00')
    , l1 f2 L& m7 M, j& c. o; N) N1
    3 G% u: z$ ^) h) P% Q24 H9 k. b* G1 T# u; P! C. \) f4 `
    3. V' }# m: E. g5 x
    4
    0 }. Q. n; y; y5
    0 f0 J  o, v; G: C6% w, }' q- j3 N6 j
    7; @4 D# @* o. d- j% S! ^5 ]
    8
    7 v% P) `; ?5 w  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    % }( @, R/ a0 R( m: D  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:! I! t/ E+ j* s4 s
    ! E7 ~1 W" P1 e4 m: e% |8 _" h
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    " s4 e5 c# |7 E$ K, M9 C+ Zdr = pd.date_range('20200108', '20200111')  Q8 n; N, _) ^, |3 o5 V; h
    6 M; x* W9 q( K5 r* Y& C6 q
    dr.to_series().dt.dayofweek
    ( U3 n3 [# S# \0 BOut[89]: , c& E( }/ F" Q- {. ^; e  c& N0 l
    2020-01-08    2! E, ~- f0 r. o
    2020-01-09    36 L5 j4 K0 U2 l# g( n" j
    2020-01-10    49 W/ z4 x( b- p, [& _. J
    2020-01-11    5+ q" w, x- O7 a- s2 L; J
    Freq: D, dtype: int640 T! G# {. u* r3 |0 k

    0 c* C) |7 M) B[i + my_filter for i in dr]
    0 v% R6 k$ S1 _% `Out[90]:
    8 k& _0 z! E5 c* C[Timestamp('2020-01-10 00:00:00'),
    1 G/ M1 Z7 o% Z2 V Timestamp('2020-01-10 00:00:00'),( U5 G3 ~9 ^, P; z) ]
    Timestamp('2020-01-15 00:00:00'),
    " \3 E7 x- t( T6 X# Z Timestamp('2020-01-15 00:00:00')]) y6 a( x* m, y7 m. d, I9 Y

    6 ^7 Z% u+ o; Y1 h( G18 Q$ i+ i9 z, U& y
    2, s; V2 j) q- w7 I8 l& `
    37 S& B: B% h9 I8 V3 U& w# ?" ~8 A/ `
    40 }5 H8 ^- k, o* j* ^/ P4 F
    5! S5 I. h! l3 U2 ~9 B+ A0 m
    6. a+ K* l5 |5 w8 M, E
    7$ a+ A; t' h* j7 H( y8 d5 K
    8
    , Q/ a: I1 |# C9; u2 W( v3 _: j8 c
    10# N1 s, N; E# o8 T: Z& R% J# x
    11
    9 a$ n6 t) C- g2 x2 P2 s12
    2 e6 M; z* D, ^131 f" `4 @! Q- X1 v1 W
    14
    - k$ ^* q$ T4 m5 `5 _7 ]" d: j15+ d" u$ }4 |, O' ~
    16
    & ]& f' M0 V0 R) L( ~8 J  `' b17
    / A1 E5 o! p6 p. W  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。6 m0 M$ }4 d3 G/ e( F% Y
    . R, J5 `' Y$ h  e2 I: A
    【CAUTION】不要使用部分Offset" a- j! S, y9 D# e9 k4 y; m9 o6 m
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。& a9 n% E, e. ]

    . A; b* [6 `  Q5 ]. Z. N10.4.2 偏置字符串$ B4 F0 Q  L3 O: v
      前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
    - `% \' U; h8 m: d
    $ ]3 ]. ^$ Y, @  v" ~% u; M  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    9 n$ M8 m" H5 S( X6 q# _2 _6 s8 }6 Z' G: F& e. \6 b* D
    pd.date_range('20200101','20200331', freq='MS') # 月初
    / Q, d  N5 z2 n% \Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')' {9 q6 |, P& f" V7 p. r
    ( H8 F& j5 f+ E
    pd.date_range('20200101','20200331', freq='M') # 月末
    % {" z2 m5 V; ~: B! U( b# aOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    : D$ |+ D/ E6 a: x" \) v" l7 ]1 O! D0 Z9 O/ C5 o4 o& G5 o; z
    pd.date_range('20200101','20200110', freq='B') # 工作日
    ' \! ~( O" W1 L: zOut[93]: ! N2 P3 J* {2 R. P' N
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',- P: n& `1 z  z6 E
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],/ y  D3 m; Y' \/ \
                  dtype='datetime64[ns]', freq='B')
    + ^5 [+ f8 k+ o$ M- m; l0 {/ R4 ~% P7 ~0 s, U1 ~" h
    pd.date_range('20200101','20200201', freq='W-MON') # 周一
    - H/ a/ k$ q+ H7 g" F% FOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    7 b( ]4 @# T( X$ m' a7 c+ h
    3 K2 C) ]; t0 \) I" Kpd.date_range('20200101','20200201',
    - {( l, p) R4 P+ T; R6 R# A              freq='WOM-1MON') # 每月第一个周一
    . [( I+ T* f. b( q1 [4 b- t, I, u# o; x! F
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    6 u0 N' L* A/ V( e# e# G5 U% X' Y* C0 t; r
    1! }" D$ ]: {! y& b6 T2 u
    2! i( c( {0 {5 F+ O, S+ b7 \0 r
    39 \! o' ~7 A! r: `& q8 a
    4
      c  F4 Y+ t; z; H' W54 H5 L. H- _1 K* v3 B. e
    6
    1 ?0 N: A: s- o* y: l. O2 o7
    4 i& K5 I! h% `1 f0 Q0 G6 s87 W* a  _1 O1 x
    90 A' h' U3 @0 F: z7 z( `7 x: v2 x
    10
    ( ^& [8 I% L/ u& w$ \110 f3 J0 ^/ {3 E) U
    12
    # p# B( s% f$ U6 }$ ]13
    * _8 g% w; ]! |9 S4 i! U4 Y! c14
    % i: J) a! A7 x- w15
    : E; m0 l; U) j6 A& o16
    0 [7 U& L( k' g* C! k17# |6 t# W3 B3 r$ f
    18; L/ u/ J3 H5 Z! N, a" L' a  _- e% P
    19& N, r8 q; E- r( {
    上面的这些字符串,等价于使用如下的 Offset 对象:9 h& L' K+ Y$ v0 Q3 E* }

    1 i9 S' A; }0 n3 `& ]$ b9 |, |! O% Vpd.date_range('20200101','20200331',
    0 U8 y4 I! I% V3 p" {              freq=pd.offsets.MonthBegin()), n* |+ P4 D3 ?; p4 u. f
    : Z0 F# Q  Y7 a1 h$ @
    Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    + l1 O- H! U$ ^% H( D0 e; B8 z+ j9 l# A
    pd.date_range('20200101','20200331',
    , W2 D' I# e+ A5 Q; j2 P              freq=pd.offsets.MonthEnd())
    & l" e1 @" R% b- g
    ; }5 u5 P# k. v9 o! U# }Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    % U5 h) b8 h: l7 b, _5 C% R4 P  r8 u4 ~) |; S
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())# I( G; r( x. j
    Out[98]:
    : u5 X3 q2 s0 G$ L* tDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',3 n+ q% P8 N9 S* T! X9 |
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],& L: O9 V8 Q! X6 h8 p3 }6 I
                  dtype='datetime64[ns]', freq='B')
    4 x9 T( d! H2 s/ m/ D
    . r9 K- Y1 Q/ ?' i/ R# ~! w3 z0 Npd.date_range('20200101','20200201',8 @" U: N, w, h2 b/ m6 d- r% V
                  freq=pd.offsets.CDay(weekmask='Mon'))' {7 Z0 p2 @3 M/ ?+ D  h
    * j3 A* u$ w- c+ E  n
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C'). _8 z  L8 {2 D# J. q& U
    # M/ p8 {( w/ r3 z6 z' p' s
    pd.date_range('20200101','20200201',
    $ u: k% d( b: T1 l& q7 c              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))" v  E4 d% s6 t1 Q- o
    ! ^- A: n& z" [
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'), r" W, a% y8 x) K7 z7 t

    * [# J) i" x& p1 X; e1# a) }" g/ O9 g3 N
    2
    ! S5 m; _. V. M7 _& i) r+ R$ M3
    + `! z4 G) W9 M+ H: z$ d$ ^4! L- J( I1 ?0 G6 w, _
    5& D0 H/ y6 J# f% `  E* |
    6
    ( w; q1 U9 O: d7
    / P% ?9 Y  K" i8! k3 b6 |8 k1 M2 s1 f% A
    9
    " P, A0 [0 v9 W  T0 @& u4 z10+ g; c& Q! \1 M2 m, X
    11# k0 E, o  D3 |# s8 P8 Y
    12
    / e: v, D; p, p) L2 ^6 z13
    . h3 b( k2 [0 ?# H6 O9 {- A$ G14
    3 X* |8 I& L$ M" E$ h+ }150 Y# G5 y" l3 R' k0 }
    16
      s: c, E5 ?6 ?! A$ Q17
    " M" v7 B1 r8 E1 q7 f9 {18) w- M; W/ A0 g
    19) U' I# q, S  G2 }' t
    205 c0 [; {: |" V* t1 ]9 f* r
    21
    8 O) x% Y1 g7 O0 ]22& H9 `0 L% @; F: N4 H( Y
    23
    " G3 n- @9 M' T7 N' _1 }6 B: V2 X24: i0 {6 A  g2 S- p4 {& \/ I7 N( l
    251 c  n# Y9 K% M, K; ^/ G) @
    【CAUTION】关于时区问题的说明
    , D. }) e$ i- f- g: }4 C/ ^' N  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
    + ?, @) I8 c2 ?$ o& e# I3 j7 X* K
    + i' ?7 S- G& T2 Y7 d) ]10.5、时序中的滑窗与分组
    & N( q: {1 W7 J+ c10.5.1 滑动窗口
    - |# @& O0 o- {8 M  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    ) g' W0 O% A- U0 ]
    + F) P; R, o2 P0 v1 oimport matplotlib.pyplot as plt" d0 U8 R; L$ o$ W  Y% t: @* j
    idx = pd.date_range('20200101', '20201231', freq='B')
    ; d: i. ?  M5 B; L7 Unp.random.seed(2020)- Q- d+ k1 M: v9 ~7 T

    - l  S3 l* g. V6 ]data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加2 N- [1 [! |, }' n. v
    s = pd.Series(data,index=idx)
    4 p) O3 u, |( b; cs.head()4 t1 X6 S, {& A. c/ i7 ?: r
    Out[106]:
    7 i& [# {, Q8 Z' D: N. o' l2020-01-01   -1
    % s; \3 Z% n  [' u; S5 r% d8 S2020-01-02   -2# o* _* e0 j: X# l( X* B6 S- d
    2020-01-03   -1
    4 p, J+ A  S$ T) U' a  X6 m2020-01-06   -1: B7 ~  T! f/ ?# r3 d6 U( A. u0 X
    2020-01-07   -2
    # a7 Q) P. {: j0 _Freq: B, dtype: int327 q5 F0 @" g: s( }: [
    r = s.rolling('30D')# rolling可以指定freq或者offset对象$ k. U# s) Y  O% d! B

    ( g5 i0 n2 Y, u. ^1 z. G% d0 Oplt.plot(s) # 蓝色线; M3 L5 e" A0 H" Q+ a
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]* w9 ]4 s! U* F: \* ]7 ]
    plt.title('BOLL LINES')
    . ~/ ?+ P  Y8 |0 [' dOut[109]: Text(0.5, 1.0, 'BOLL LINES')# M/ g( u! i3 S1 \+ b. K* h$ }
    0 V, Y3 U6 V8 ^
    plt.plot(r.mean()) #橙色线0 {, Y/ A7 V& b9 t- X1 Y! H! L$ S
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    - m6 f7 ~/ F( H) R9 F) R% W/ P* b
    ; B+ n3 O, ?6 A: Y* G4 u8 Y! eplt.plot(r.mean()+r.std()*2) # 绿色线1 ?6 D. C, n3 C4 X1 m7 h" C
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    ; X& T: A, Z# S2 w9 r6 ?9 r0 B
    - }. h7 u& A) p2 _. N- [plt.plot(r.mean()-r.std()*2) # 红色线
    ) n# ~( o1 ^2 `; c2 @* gOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]# }* o9 P! }- V5 Q3 T9 K

    % n) ^6 K3 {) a, R1
    % ~8 D7 i. v( U/ d4 b+ q9 F* ~" R2* o% J- r; l3 Z( V
    3. A3 H6 s6 E% j3 g
    4+ k0 t# d0 D/ F
    5
    ! G% Q' `) A( m6
    ' z. J2 y2 m+ Y5 z* V, O/ i7
    # v- b" ]" O% i* b8
    4 ]* i" ]" e! m4 k- a- _8 d9  l; h5 n7 W' O# y5 v6 ~7 n0 a( p. _2 r
    107 K$ A4 p. V! S) @* o* L
    11
    : B! j2 ]' r, C2 o' Q12
    & o- @3 i1 t( |( W, N13
    5 v3 U( f4 o# h14
    0 h4 q" b7 ?8 p7 x6 a+ o8 C15
    8 A( G) a, W6 |$ n  @  s: l: {16
    3 D3 \$ D* g6 T17
    : V) y# ?1 F4 P& p0 |18* v  O* c, y! @& }: Z4 m
    19
    # x/ ~3 e5 F0 k* h3 I% g6 c20/ u; g' ]2 a8 x# s( T( v$ }! S. I+ j
    21
    1 f- G- ]# D' h! v# Q22
    * E( {; b7 a) ^; g8 Q8 i233 M4 N6 k/ I+ U0 l( A; y6 C* j
    246 }  i1 I( W; [
    255 Q) |' n) m) V9 B
    26! d* Q9 ~$ w  i9 d+ {
    274 P, a+ ]9 a* s) }
    28
    4 X% p* J; h$ O/ ?$ ?0 i29
      G) I7 O$ W, o! W' J$ B9 k8 s1 \) W& c1 S( F+ j- M- d
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。3 [( V- Z3 M8 h; S! X
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。8 K8 H7 W; m: h  t! z+ t2 x/ {' N

    * ^9 b; o+ a% O( lselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
    . P* {+ ^* X: M6 ^  ^$ Xbday_sum=select_bday.rolling(7,min_periods=1).sum()# M' q% T  N) t  m0 v& D
    result=bday_sum.reindex().ffill()3 {# P9 T+ F+ M- Y4 G
    result
    3 U4 Q& X* `1 c- w8 J4 s$ R, W* ]/ Q% O" z# l
    2020-01-01     -1.0
    % D& o( f- V: {- F2020-01-02     -3.00 \2 R* G) \& C. b4 g& B* S$ x
    2020-01-03     -4.07 k' _! a# \: L" J! L
    2020-01-06     -5.0, H# C: f0 z* K
    2020-01-07     -7.0
    / s, w5 `- q9 L5 g              ...  " K2 d1 w0 d$ S. \
    2020-12-25    136.0
    " Q% Y# n9 g6 [- P% b2020-12-28    133.0
    + O  C6 N9 N: u  Q) i2020-12-29    131.0
    9 I) {  f' F  l2 x1 s/ B2020-12-30    130.0
    2 n/ Q: b& V! ^8 A2020-12-31    128.0, H( |4 ^2 _: P% _: K
    Freq: B, Length: 262, dtype: float64
    ! e! n" H( c1 M0 }' u
    ; G: q% u; U. Y4 s* M$ Q: G& l5 F3 @1
    8 H) M3 e1 C  S# ~0 E5 D; n7 ^2
    7 q  F, N& ?0 k; z+ Q3
    6 z* b7 ~' [1 y: q. W  t: z4
    , _9 r  Y; R* p+ C5/ N- Y/ s/ f( B4 _
    6. d7 N$ A) ^9 X1 p8 }  _) W; ?
    7; M# \8 d9 S: n! J& T
    8
    + u, S) B( w" S( }9* b6 e7 ^" H3 I* V. W2 r+ v
    10  {1 ?  |* u) u6 f) w
    11! X" Z* N( R2 m; U2 l
    12
    5 ^2 w" z" q  d: }  u9 L5 }13. H, |4 w9 V. {
    14* M1 Y, \/ Y1 f" R4 E# A' `
    157 _9 Q( z0 K5 r' e5 X3 q
    16+ P+ e# M2 |; V
    17' m9 d0 r2 m% k
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    8 S# ~- G6 ~# T7 Q" L/ U* n* S# ?1 X, f$ m: `* m9 l
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    , q# I/ u  k# X: S" \" D1 C* ?. \* g) ]6 m3 @' e1 u; w, @
    s.shift(freq='50D').head()
    + h7 y) x* D- r; YOut[113]:
    6 @. C4 r/ ^% Q; q% R9 R1 K2020-02-20   -1
    7 f. j0 g& P% o; {( I$ g% L2020-02-21   -25 m2 p6 u- u; m/ w/ F
    2020-02-22   -1
    # b1 I( A( @7 F1 r% g* Z2020-02-25   -15 k$ z: a7 A6 J9 k
    2020-02-26   -2
    6 ~8 y) j; b9 Q( X+ j! Ydtype: int32
    . ~- y$ U: {3 K( P4 P9 a; g1' e% F7 K; S$ \
    2
    % D, M0 x: }2 |+ g3% R- r6 `5 I( @
    4
    7 z8 }( k4 \: F1 ?3 f58 o) _% G9 \9 ~( U* E
    6
    9 Q* j- E  l/ H  [7 ]4 L7, j0 t' y7 J/ H& A; o) y. X, r1 j6 c
    8. L5 O$ s3 {! r9 s# }
      另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:/ q) h" g/ U) r2 G
    # N' q/ d4 B3 @, m
    my_series = pd.Series(s.index)
    . `$ H: b0 h1 p8 E0 ^8 Amy_series.head(); J( B8 z/ @+ R, D7 V
    Out[115]: ) C$ y5 `( B( ^* L9 c9 s+ D& t
    0   2020-01-01
    : b, l7 Z" I. p: v- O  F1   2020-01-02
    - F5 V& j2 H1 K4 |2   2020-01-03' H) x9 K4 T( j
    3   2020-01-06, Q. g  [  \* t# n5 O
    4   2020-01-07" o) z7 \7 u7 \" Z  ^4 s& |
    dtype: datetime64[ns]6 O4 p, `5 o  l  S5 o9 G
    ! I  b; \. Z. b6 I
    my_series.diff(1).head()6 B; Z0 ~1 q# i8 W# D& S7 ?
    Out[116]:
    0 V2 ]  Y+ \. ~5 [- B0      NaT, T! [" T9 r# O. `% U
    1   1 days
    1 J3 F1 V" @! {  A0 M6 l2   1 days
    5 Z5 u6 i5 c) {( }* l3   3 days. B# ]6 p! n) N# v4 O
    4   1 days' P6 U. N! D* E8 N4 i; `
    dtype: timedelta64[ns]
    6 l1 V5 C, I0 p: \$ W1 [
    + @7 T4 E0 q9 H' @" M7 t$ V13 I$ w0 q5 j4 M( q2 Y. w
    25 ~. u+ c( Y1 \: p5 G; ~
    33 N! c# |) g! x% D. a
    4) C$ M4 Z# b9 T# m( G
    5
    - y* u$ d, Z( {# n! G( L, K$ J63 e% ^* @( T& W& _
    7
    & |- [9 u' X1 u) n. R8 L) y8
    2 w. m" z7 v4 T. B/ G, F9
    : b6 L+ W: E) H  G& p! [106 t8 e6 \; s+ Q$ Z! g1 l
    11' m3 ?, Z1 l. I
    12
    - c3 Y1 \* [" I) m8 P13, I8 Z1 o# J" L. d# {9 h
    147 t, w) S- L0 Y8 X: [3 ~
    15
    # L4 L0 z2 ~' N) }. f2 j160 S5 r9 |6 @: `! S4 F
    17) \4 T3 J/ b# f* c0 t
    18% ]! w, n6 s. z7 D
    10.5.2 重采样9 O3 j8 X% j4 j
      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)
    $ o" q/ L, \# H常用参数有:
    7 p3 ?) o$ v# v; H9 B1 e6 t, E6 X9 G, G! J+ B/ Y8 ^
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象6 C% e- e9 I* z2 U# B
    axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    ! f% G) O. V% Y. d: S( Xclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。% H( Q$ `! [) ~: G
    label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。5 h$ t5 Y/ z8 r
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。9 N: Q8 B; q: H3 ?/ Q, n
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    3 }9 T8 D, k7 _) [level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    8 n0 U3 J9 t# P) K2 y9 ?8 vorigin参数有5种取值:
    8 `. @$ B$ M: ?9 X& T% ~# c) m0 V‘epoch’:从 1970-01-01开始算起% A7 T2 Y6 i+ g% k4 d' M, T2 H
    ‘start’:原点是时间序列的第一个值7 ?$ t# t. l% O  e7 P) G% l
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。
    ; g) q" r- ^; Z# `'end':原点是时间序列的最后一个值(1.3.0版本才有)2 F! O, l/ j3 a# o' v! m) {! }- b
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    % O  C# R- A# U. eoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
    # M, Y* b5 ~7 I7 b5 P! n  closed和计算有关,label和显示有关,closed才有开闭。4 p) U. L4 ~) X7 b& a
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    % t, J& _: C7 o) _
    ( x7 z3 a  R: A. s6 @( L% w重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
    5 s  i; _" R3 X7 R, u7 Ys.resample('10D').mean().head()
    ; i. u0 ]; j# K9 V; ^6 DOut[117]:
    6 L! k- y: p! t* b8 P2020-01-01   -2.000000% E1 q# [  ?" c- V3 g7 Q
    2020-01-11   -3.166667
    # s6 P! H, r- ?, N) Q3 H" _- h: \2020-01-21   -3.625000
    ' `1 c/ z* y1 n3 G3 y- A% l- y/ R/ u2020-01-31   -4.000000& D) c7 x; u$ X) }5 y! T# ?
    2020-02-10   -0.3750001 O& C+ m3 v% {# z+ F" K
    Freq: 10D, dtype: float64
    % F! Q) m+ _$ ]1
    + b4 v& o5 G; S9 Q" K% E2
    1 q) `8 T  x6 M( Z# E! N! }4 `3- u* N8 ~  |# {2 j! J; {
    4
    3 z5 q$ c* H- [/ {5
    9 M, ^) o. {: X, q  M' M5 l3 E6
    ; u" Z' B$ e9 \0 ?+ F7' K/ `$ Y0 X$ F4 o/ u$ v: B& C; r( `
    8
    % f1 x8 ]# M* P1 S8 ?1 x8 S: i可以通过apply方法自定义处理函数:& b, a% G: ^! m
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差. Y- p4 t0 E. t. F% h( X

    ! U9 {& q: i+ E0 {6 y: i; J# qOut[118]:
    5 m; K) X' \- f$ o' ~2020-01-01    3
    ( ?0 u3 a; r: {9 r& k  g8 I2020-01-11    4; C9 y# v4 n2 U" I
    2020-01-21    4
    3 x& b+ {' A- }! P* C' \: M  @2020-01-31    25 I& ]6 {5 u) e; G/ v+ f
    2020-02-10    4
    9 d. D2 D) D, ^0 h* S9 |. J4 DFreq: 10D, dtype: int32/ @" @) u0 g! g3 K3 |+ g" i5 D/ {
    1* b! _2 p6 [" y3 |( v
    2
    % t# K6 ?  G3 c" T8 w  R3, ]4 H0 C# }" d0 K& t
    4
    8 E" ?8 F- B- c5 B55 B! D' @! L2 [3 `
    6
    $ g' ]! J. }2 s1 U; Q6 @7( ~6 a, I# v! F
    8
    0 U! t# c0 {% A1 J0 O: M90 K& {& b) o; I! z' w
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    ! U2 L; z' ^3 B+ U: h$ Z/ B- h2 G' |. x1 J& M4 w8 y) M- c& |9 f! w
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s'); @- Y3 X2 b1 l6 A
    data = np.random.randint(-1,2,len(idx)).cumsum()( G/ U' Q$ B9 Y) K
    s = pd.Series(data,index=idx)2 C1 b  u/ |* X
    s.head()
    , m  U( y! |" ^" N  N
    $ d, g  j/ k: v: S; O+ V) oOut[122]: 1 f5 E, N1 j3 p5 s. t$ x& O2 b
    2020-01-01 08:26:35   -1
    4 @+ i( A6 {: @# V4 y2020-01-01 08:27:52   -1
    ' \  k* n' V4 J- F$ w2020-01-01 08:29:09   -2  I( i( `+ C4 M1 `( e
    2020-01-01 08:30:26   -3
    5 L& t! Z: q9 K5 O2020-01-01 08:31:43   -4
    # u9 c  P6 \1 l" U4 ~  g7 OFreq: 77S, dtype: int326 L9 ]4 n/ g% \2 @$ r, c# N% \6 R
    1/ [3 k+ j/ z/ k9 n# \3 W
    2# s7 Y' f% e; f
    3
    " V3 A, p  I) [7 L! |! W4% E7 B8 p9 a4 _+ f- ~# l
    5* j. t& n9 B  F& I
    66 ^9 B$ r) g! K/ w: ], E. M
    7
    ) g8 V2 m4 V9 w: D! Y9 X8+ Q7 U/ u6 P8 H8 P
    9
    ( O5 ]) a5 u" _( B6 E+ ?& v10+ ]' Y$ L+ q( |  b, k
    11+ I2 Y- q& T% G& ?" P
    12( w$ z+ p! j. `9 i' \: p5 ^! h
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:1 y* u  H4 s& H+ o8 C. w3 P
    9 c( ]3 J9 ^0 C6 y( {( L, ?5 A
    s.resample('7min').mean().head()) ^7 B& }4 |" |$ V: c5 M+ @
    Out[123]:
    ! t) y+ {6 z+ A* n) {1 L( S2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值4 ~* p% M, [/ O
    2020-01-01 08:31:00   -2.600000  o6 E; @% y5 N) u
    2020-01-01 08:38:00   -2.1666672 p# S: g4 [3 c1 s' B
    2020-01-01 08:45:00    0.200000
    9 ]8 y. k, d% h0 N% k: b# l2020-01-01 08:52:00    2.833333
    ; ~. |* `$ P' Q8 SFreq: 7T, dtype: float649 l' F  A8 t- t; _
    1
    ( W, J1 U9 N3 J* e; L2
    6 B% C7 m/ O0 v' l3
    2 j7 t) B# n/ E" t4
    , `+ I2 j$ m) e' R' _5
    2 j$ v0 m+ O6 q  U) }- {; o$ z1 x6/ ~' M! Q; W3 k/ {
    73 s( [/ ^6 H% N. y- s
    8) i9 }* `. Q, x5 }7 o" B& w  j
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    % Z# W0 @+ w6 T( c6 F
    * u& |  T' x, E- M3 bs.resample('7min', origin='start').mean().head()9 t% q+ `% Y9 H4 u3 O0 g
    Out[124]: 8 s3 K& D+ G: l9 s+ R5 E4 \
    2020-01-01 08:26:35   -2.333333
    ' Q, I6 @6 w# U  a5 A2020-01-01 08:33:35   -2.400000
    5 x3 Y! j, e0 }% J6 _" `( G2020-01-01 08:40:35   -1.3333336 `- ?& [$ j/ _1 s+ ~& p0 U
    2020-01-01 08:47:35    1.200000' N4 [/ [# S& k6 l- G! \+ h
    2020-01-01 08:54:35    3.166667
    2 C) @: f1 O7 F9 `4 B) x/ ~Freq: 7T, dtype: float64. v: u$ p. |$ v* `8 _; y8 |
    14 W; Z1 |% R) B& [% a4 y
    2
    6 G( V( Y6 k, G2 F5 [3. m0 K+ t5 R& f: I/ p  f; @
    45 k1 s" T5 P5 v
    53 V) M: B* F# F; w' ?
    6+ r# U: j( x0 E( ?9 V
    7: g& G2 e6 Z4 x2 Y. C! J! j& h
    8
      h" R6 |' ~# g  N  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    1 @; S+ g! e5 Y3 t, w4 {- z9 F' g4 i5 P3 ?, E) e! N2 p
    s = pd.Series(np.random.randint(2,size=366),+ F0 j. h6 A9 r+ b8 E. D6 u
                  index=pd.date_range('2020-01-01',
    & j' h) ]" M6 {; Y! r8 o. V7 p                                  '2020-12-31'))
    ! P6 ]1 Z' Z) j2 I
    + U' S: o$ H0 k% s' @! Z0 D  Q6 v) u( v  `; K
    s.resample('M').mean().head()
    + r0 D2 p% v7 G2 f4 f$ g- }1 }Out[126]: ; G4 _( h* W" F, v" h
    2020-01-31    0.451613
    # X; z; i7 r( d5 P2020-02-29    0.448276
    7 a- w$ K: C; v' w2020-03-31    0.516129& [4 y1 h8 S9 e7 L( |
    2020-04-30    0.566667
    9 ?4 a. ~. j# S7 B) @7 v2020-05-31    0.451613
    ) T  c/ h/ j8 x3 tFreq: M, dtype: float64" \8 s  q4 u& A# B! A

    4 b& E6 k, H6 L  r* P( ]- Ts.resample('MS').mean().head() # 结果一样,但索引是跟正常一样# [6 R  R0 b8 h9 i  _
    Out[127]:
    / N& {" p% R4 s" z2020-01-01    0.451613
    ! A3 l5 Q! t( I; z5 X# E: b" f2020-02-01    0.4482768 j% o$ s  a5 `* h
    2020-03-01    0.516129$ v+ P' \; B  ^+ V' @
    2020-04-01    0.5666679 @" z6 Q. T9 M0 R, q) n3 c
    2020-05-01    0.451613
    8 p  a1 g" \8 l  [% IFreq: MS, dtype: float64
    - s/ V  @; ~) \2 `
    , ^, \5 x! o6 b1 j4 p: v1
    3 p8 V7 b5 x, o  L0 V3 b2
    4 O9 F' l. C- }" s, l3
    & }& h$ d1 [# x% j. q44 H# J" y5 H) w6 a. m/ ^
    5* _: C% v. J+ R/ w" C
    6
    1 F6 f: b! P) L7
    8 Z( S( O' e: @2 _0 [83 ?/ }& z- ?- c
    9
    ; K' l1 o$ q' V' B8 f10
    / z5 L" t3 ~' R/ h0 o3 t6 Q. ?# v11" j# `4 p. {) I1 x$ Y0 Z
    124 C" |! z5 S6 g2 k+ g
    13
    8 h! u$ m5 M3 K) z9 I4 f. P9 O5 {14
    / ^5 V0 ^0 R5 s( y7 y* O15* W# J2 v2 Z. S4 e! G
    161 Z( x5 H, z3 f3 O$ X+ z7 p' V
    17
    6 r: E9 J& x& u9 M. l3 l" B" y. y18# ~8 a: d! a1 }! n% M. @- r4 G
    19
    . |: _, m& d/ [, H20
    8 v' J6 u2 Q# w5 R21
      U6 O# \8 \9 n220 |& W2 A, u# \: D
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:9 b6 y/ Z' _2 q1 ]! S
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    ; h7 o/ i1 S% Z+ w     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}7 U& l& Y; b" X' @5 Q1 I3 l, n
    df = pd.DataFrame(d)
    5 n2 t& v. l7 ]8 l. sdf['week_starting'] = pd.date_range('01/01/2018',
      k$ N! M* ^9 ]; o4 G                                    periods=8," O* l: y+ G% R
                                        freq='W')0 a" ^# Y0 j+ o$ N+ R
    df
    / u. ?& J' u( {8 m, Z9 {. B   price  volume week_starting
    % U4 C9 S7 }- u2 V. `0     10      50    2018-01-07/ P+ I2 P( v" N5 M5 W
    1     11      60    2018-01-14
    ' U" m9 k* T0 \; P2      9      40    2018-01-21* q: a" M- k. a3 o& O0 p" H- V
    3     13     100    2018-01-28
    ; O/ B8 A5 \) b4     14      50    2018-02-04' Q% `* C0 I7 k  Y! s  l
    5     18     100    2018-02-11" a* }- V' j* d
    6     17      40    2018-02-18
    4 u/ V( I3 g( X! x+ f7     19      50    2018-02-25& s! w  d! H/ p( @- L! K
    df.resample('M', on='week_starting').mean()( o6 i- {8 {$ G+ e: ^8 Q: l
                   price  volume
    . _! ^5 H/ k& ~+ }  Sweek_starting( S2 k5 i! w6 x
    2018-01-31     10.75    62.5
    : Q- h' }: W# l0 a* m7 H2018-02-28     17.00    60.0( R. C9 L* k7 N' ?1 B8 l& ~

    ) e& B# g; W4 L3 K15 o  u, @+ H3 O) u/ o
    2/ ^$ Q9 J3 H  ?1 W; m
    3. q% z) A  V; d" }$ B
    4
    . w7 G2 u! @0 G0 I! U9 y9 J3 g5
    & x6 l; @8 _2 J4 ]% b6
    ( s. o+ C& v. m7
    / q  Q0 i; ~7 s8
    & D8 v3 v4 Y1 n5 f7 s% f) _' u6 R9
    % x+ H, p0 @4 y7 V1 n# F10
    . y- k: d4 E* k$ a1 `11" |$ Q! H8 K6 \1 H* e! v
    12# F$ `; h! O' p+ j  k. J- Y+ ^
    13
    9 Y% y7 H# T! R3 e! f9 u5 J14
    9 I6 _3 A4 ?8 h$ \15& ^, r3 \, |0 |% {2 G: d
    16
    1 @$ Q' K" W- D, k# p& K17& v! A2 F+ _$ ~( S9 [& [( E4 W. o
    18: a+ g6 i- o. Z6 w9 R7 }7 _
    19
    7 Y8 ?' q/ j' e* i3 N- V20
    : J$ c, Q2 @" B$ V8 J- `. Q21
    " ~+ |/ _7 ?# `) l& w- `对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。# t; W' ]; v* @( H& j$ C3 S1 f
    days = pd.date_range('1/1/2000', periods=4, freq='D')
    , k+ l4 W  Z, H8 {, nd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    ) q, W  w# w1 @0 c      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    - @/ Q; @- x& D) z/ K. pdf2 = pd.DataFrame(
    4 t8 s% A$ h. Y    d2,
    + a5 s0 W9 {( q    index=pd.MultiIndex.from_product(% e- ?+ L" C: Y7 y- z' x' P' F0 W1 F
            [days, ['morning', 'afternoon']]  E( Q! g. g: v- J3 Q' c) R
        )
    # \1 T' k0 Z1 k& V5 R/ p/ S% K+ K)
    " h5 Z2 _. D5 [$ p; a) W$ t+ udf2
    5 U4 n) ], ~1 n                      price  volume; d1 H4 \) N7 `# p, C: D1 {! M
    2000-01-01 morning       10      50. Z7 l7 J, O' @: W7 W$ C& e: G
               afternoon     11      60$ _1 _: ^6 F8 ~+ V( f7 u8 x: T7 a: c
    2000-01-02 morning        9      40# _4 q6 a9 R9 p! y6 j2 V
               afternoon     13     1007 t/ m! b$ B% C/ u1 o6 P2 C  o
    2000-01-03 morning       14      50) Z- E1 O/ U& V4 Q/ ?" x7 P* ]
               afternoon     18     100
    5 v5 d$ X% {' F; X/ V/ i! A2000-01-04 morning       17      40
      B$ y# O4 d+ ~           afternoon     19      50; B" ~$ Q+ t4 f% E7 M
    df2.resample('D', level=0).sum()/ H! p8 j& J! }" I% z2 d
                price  volume: @6 y. X1 N6 @. H
    2000-01-01     21     110
    5 A; x& T$ q. X) W$ Z7 b2000-01-02     22     140; c5 K& U" r) y  K* B
    2000-01-03     32     1501 P, e: |0 g3 e: U8 a9 C  Z
    2000-01-04     36      906 t; i5 ?3 I: ^
    + b3 g- }+ \  w1 u) q8 B: [
    1( Z* k+ K; }* p7 Q) j3 V4 v; S3 d$ ]
    26 o4 I, q5 q( }' o* ^
    36 [) H2 N3 k  U# Q2 J: X
    4, d) B3 H9 }0 x7 A
    5' d  X3 u+ ?4 a) c
    6  q5 f4 v9 }; Y$ J
    7
    7 o# [8 V" I3 A/ F1 k( y80 u" w) r; L! u) M( H  p
    94 Z0 G) X% Q0 w
    10. s; Y/ H: l2 W$ t0 G
    11# |4 g  |. g' I7 C( u
    12
    & r1 Q9 v  {4 g133 H7 m' P3 D: Z4 Q4 Y. H. g
    14
    ; {0 K- G) A3 V+ p15
    9 I; r- G3 s' b( p( q16: _; _$ j) [: d9 j( O: s
    17
    ( D2 c  d  P2 ~7 a$ f  D0 Y5 T18
    ) C" u# J8 m3 }5 k; `19
    ; `) @% _& E2 ^6 j% t20* o" w, K* N# u, |
    21: M- K0 ]* N5 t- H. Y" c+ V8 ~
    22% s8 K. m+ P% h0 P5 Y( e% ]
    235 _' w: K6 E/ D5 o- k
    24  }0 V. h1 K. b$ \& }# K. t$ I
    252 Q. B: `0 t. o' U) m7 O" ?  M) [
    根据固定时间戳调整 bin 的开始:% @6 X* j6 b5 l* e6 V& {6 T
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'! p! |7 {5 \6 b) l, e- l) _
    rng = pd.date_range(start, end, freq='7min')
    3 ?6 q, W. @) v' K5 o! T+ Nts = pd.Series(np.arange(len(rng)) * 3, index=rng)3 `7 z! ^7 Y7 {# A
    ts: K, Z# K6 O5 r# p3 y$ S" {
    2000-10-01 23:30:00     02 m% C2 P% I1 k6 g: \
    2000-10-01 23:37:00     3
    - H, `8 U! }8 o/ i! U3 v! t2000-10-01 23:44:00     6, q2 o: K5 F& r# E! K5 l! l1 `, T
    2000-10-01 23:51:00     96 v, A; M  R7 l; J0 d! n
    2000-10-01 23:58:00    12+ s* X: Z/ W0 K) r) @5 _, E
    2000-10-02 00:05:00    15
    ! c) [& N! B& P, Y2000-10-02 00:12:00    18
    , N: h# M# G1 M8 P2000-10-02 00:19:00    215 |( ?6 C& O" v# [! w
    2000-10-02 00:26:00    24
    * d8 A, ]; X2 ^+ l& E9 @Freq: 7T, dtype: int64
    # G6 _1 s0 M# E( V
    5 N# Q3 T  {1 V: D; xts.resample('17min').sum()! f" _: k" K. L7 b$ h2 a1 ^' h  g
    2000-10-01 23:14:00     0
    ( A# ~6 `5 d" h  d3 U& j2000-10-01 23:31:00     92 M! L5 k5 W2 D" q: F" b  R5 U0 O
    2000-10-01 23:48:00    21, i7 w6 x+ s  x# B7 e/ w
    2000-10-02 00:05:00    54+ N. A, _& U' V' p7 l  v5 [! o
    2000-10-02 00:22:00    24
    # C$ c, K1 S" `3 _8 r9 Q7 T0 RFreq: 17T, dtype: int641 |" A' z+ `) F

    - \& b3 J2 [' kts.resample('17min', origin='epoch').sum()
    ) W! S: w( o! e' R6 g1 c8 r2000-10-01 23:18:00     0
    % |* z1 _0 ~; F* e0 R: ?2000-10-01 23:35:00    18
    9 l, P5 z" w6 \" [/ r  I2000-10-01 23:52:00    27
    + f3 P3 C$ W0 h$ ^! s2000-10-02 00:09:00    392 O6 g5 b" i" X' Q; A& i+ Y0 m
    2000-10-02 00:26:00    24
    7 P9 S5 q9 f4 m4 L$ ?0 C# ~Freq: 17T, dtype: int64
    / [3 D5 c$ g, z# m# Q; F: P3 S+ M% r& O2 b& O9 j
    ts.resample('17min', origin='2000-01-01').sum()
    : g! s8 G0 v4 a" Z6 D3 G2000-10-01 23:24:00     32 v# e. r8 H( R
    2000-10-01 23:41:00    15+ |$ h) i- s4 [
    2000-10-01 23:58:00    45
    : y/ {% V# H; q. [9 m9 z2000-10-02 00:15:00    45
    1 k9 z; t* X/ Y# vFreq: 17T, dtype: int64) j! l! ^8 a, L. u  |

    2 z  R7 {# P# O6 `. }5 }' M1
    3 n3 J9 e* y1 T% Y21 t0 g) z) x( t
    35 n8 ]# k* {3 g) q- F1 d
    4) w5 [) n8 t+ E3 {# s, i7 l
    5& g1 w; W& U' X! E- p! v# s
    6
    + s0 q; F3 H9 J1 B/ s; O7
    4 _3 X2 b" i: Y+ ~) u8- |" u* \/ Y% J) ]
    9: j( N! d5 @; w
    10
    - p  L2 h1 d# s0 Q8 n/ a11
    ' i/ J& V: ?+ s' ]125 t% Z0 F/ A6 Z+ p; a
    13
    7 n4 p) _0 ?) k  Z2 _14' T; C! e5 J# a% Y
    15
    * k- b, j. @2 ?6 J# B' p161 a0 ~1 \  d- [$ w
    171 T! ]8 d7 ^. C' ~+ b2 A+ p/ [  ~
    18& M' R- z* N% t' K( r) V& ]
    19( l  Z/ v& s2 i* a, e; n7 q8 b
    20
    3 A. O8 m6 o7 q4 [: P21
    ) L7 Y& D/ r8 q. c22
    % y1 X3 }9 _; h, s2 ~7 s23
    / {5 F1 w  B* r* j$ a8 E24
      @, s& {8 |8 t, T& G0 _25
    3 K/ j" W- Q& T26
    5 O# R6 S, h" A! A/ B  a27
    * C7 B" Q7 p% {, d: t4 B2 p6 ^28
    2 U8 f- e, X3 d29  J, |$ k3 o  i3 o# q! M
    30% Y+ \8 p) g; }$ R6 M% G
    315 N4 Y* k' W  p) u) U5 l0 s( S
    325 B* H7 G& s0 H0 h
    33) `- Y2 h* E5 {! S, ]
    34% N; D" j" u2 n( z' E9 t; t
    35" W" p( ^; w- E/ h3 l
    366 Z5 n( ?2 c3 w/ p+ _1 r
    37
    - e" s3 b- H3 O( u! N( i0 P4 T" ~如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    2 I" y5 j! N% o8 s' _% Fts.resample('17min', origin='start').sum()" V: @* p4 K7 z1 a7 {$ k& y
    ts.resample('17min', offset='23h30min').sum()
    " o* y: [1 n+ h! L% y& E) U2000-10-01 23:30:00     9; @& ?3 Q% h* n/ z/ u5 d
    2000-10-01 23:47:00    21% `% q$ k# c# m4 y; A; I
    2000-10-02 00:04:00    54
    6 U9 W# t- d$ M( B& b  n2000-10-02 00:21:00    248 l0 f) P& I7 @: |9 ?9 ]# d7 ~
    Freq: 17T, dtype: int64
    7 ?# b- G4 n" k2 X' g6 ]1
    / @( T- d! V+ N2
    7 f/ s* f+ k* e3
    9 l0 j0 i. ~- k# ]7 D2 b4
    ! E6 W' e1 W2 K$ J6 M1 Y' ~5
    8 @# f: T2 U, p! ]1 B% D  a1 @! q, ]) ]6. ^0 u' C6 ?7 A! F. k
    7( I6 l* b, _! Z! r2 y6 {
    10.6 练习
    ' }$ {6 O# E) {7 N* w" R, Y" [Ex1:太阳辐射数据集
    ( a% l; ]5 |$ D) ?/ \& q现有一份关于太阳辐射的数据集:, ^4 y& F3 m' z' K, u9 M% P" [3 h- e
    3 F' v$ r% b5 I3 l2 m5 U7 A& O
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    0 N3 O$ T, w' f; Fdf.head(3)
    / H$ a8 K2 Z6 I( `
    0 r/ M" d0 S, K+ }! k! [0 WOut[129]: # g0 D& |# S5 ~& ~( l2 ~8 {
                        Data      Time  Radiation  Temperature
    : ~* K7 \/ I" i4 @' H1 {/ a( o% t9 Y0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    9 @9 O# s( V4 z1  9/29/2016 12:00:00 AM  23:50:23       1.21           48; C& V) D# _0 L8 A5 }8 S
    2  9/29/2016 12:00:00 AM  23:45:26       1.23           483 m; t/ w: ^+ n/ V
    1: q/ \/ V1 k0 P
    2- S# e) m! R6 z: E
    3
    ( \; d) n, D; D' p4+ l- u( E5 |  q
    5
    + z0 S  P: S% I( U! ~6
    " F* }+ Z; K: [; W; {+ `- j4 {7
    : v4 ~4 P# y7 J* d84 e- Q& j$ U  M% V/ x# S6 P
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。: U# B5 _1 r2 p) ]
    每条记录时间的间隔显然并不一致,请解决如下问题:+ z2 o' }2 }- _- s+ J; D6 P
    找出间隔时间的前三个最大值所对应的三组时间戳。2 m5 V; {+ d* u% k/ m
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。0 y% S* d( L8 W, `0 P0 e
    求如下指标对应的Series:
    . a1 `$ @" Y3 w3 f1 N温度与辐射量的6小时滑动相关系数3 |/ U0 |; m% I( u- w
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列5 ~. N9 _) K4 Z' N. G2 Z
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    / r  O0 j7 H. b4 \5 dimport numpy as np
    - p. V2 g7 h2 H9 I" {import pandas as pd: l7 s# C5 |. z6 u% z
    1
    , x* k3 U, F# Z- w) h% \6 \7 f/ W2
    0 I; B2 n  j/ S1 Z将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。/ z3 r  O% y7 I/ s
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
    5 M' w; }! u+ ]6 Y! t1 Itimes=pd.to_timedelta(df.Time)* Z# ]: N- Y- X* H8 X5 F- E' J
    df.Data=data+times: }; t* g& @. O6 \9 l4 i9 h- y
    del df['Time']
    , S7 N3 W# B, R6 H; T7 ]% j0 L% u: k) H+ Ldf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    : H; p/ b& P" L0 ?9 {! wdf3 ~* \' c  l9 i  d) G
                                            Radiation        Temperature
    $ `* ?! O( W) G1 [Data                , r7 e, i8 }/ E) j6 |' W
    2016-09-01 00:00:08                2.58                51& Z  z, {, Y  I+ W; C2 C3 {! I
    2016-09-01 00:05:10                2.83                51
    ) i2 K/ t% h, I. d9 o* H8 [2016-09-01 00:20:06                2.16                51& ~) A9 C# ]- V- ]7 O  ]& A
    2016-09-01 00:25:05                2.21                51
    $ l) B5 m& @6 s& J2016-09-01 00:30:09                2.25                51& k+ f6 r$ h! M7 D
    ...        ...        ...
    * q! S7 x8 M5 H" W$ }2016-12-31 23:35:02                1.22                41+ O$ ^; n0 o8 t' Z+ `4 U! d
    2016-12-31 23:40:01                1.21                41: r2 u  s7 D  a5 }" ?
    2016-12-31 23:45:04                1.21                428 l3 d" d+ K8 r. P
    2016-12-31 23:50:03                1.19                41
    7 v  Y/ X6 {/ ]1 |' R& j9 g2016-12-31 23:55:01                1.21                41
    ( T) ^4 m3 F5 E, n$ o+ ]0 e4 N
    4 m2 Q* |, w0 F& u12 [$ `; r2 u7 p: N9 P1 }
    2
    & x* ^" X3 _4 `& H' Z: p0 K  g% V3) v- p2 j) V' K
    43 z3 R. t/ r. x+ F  j
    5
    * B; V3 I+ J, c9 M4 a8 U6& R& ?" T( \  {6 A
    76 x+ D' l7 p! P. x4 `
    8! r& L. X. y, X% a$ j! A2 b" _
    93 B' I- G) k+ v6 D
    10
    1 x3 H- R2 X  F; A11
    1 q$ a; Q1 c6 t$ ?, i8 N12
    # M1 c4 }# k8 X0 D( @0 ^13/ c& `- |* r  J5 K
    14! y! I6 ^$ @. G) ?/ ^  l
    15: O* j/ z3 q+ D/ _" p8 n
    16* B$ _/ N0 m& {4 d: f, C
    17
    # x5 a+ g+ ^0 T; J6 |" |$ ?, S) i18
    , `0 ]" m9 G8 X& d0 L5 b+ F19
    4 e. n9 {8 X# Z3 h: l2 S( P+ {  Z每条记录时间的间隔显然并不一致,请解决如下问题:
    ' ~4 x1 @5 J9 Y; d  f" d找出间隔时间的前三个最大值所对应的三组时间戳。9 H% m- g( a1 A) w
    # 第一次做错了,不是找三组时间戳" M7 N4 c( t* `- N( C! t
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]+ Y1 G. C" g& i1 B6 k. G2 Y9 q
    df.reset_index().Data[idxmax3,idxmax3-1]
    # G4 H- J: A4 ^" w* H& C& d2 |4 j# c0 y3 S3 f: ]$ s5 V
    25923   2016-12-08 11:10:422 P6 i0 Z9 y; B9 j# F* f( l2 _
    24522   2016-12-01 00:00:02
    ' A  W8 N6 b, i: z7417    2016-10-01 00:00:19# W2 @  K* X1 t
    Name: Data, dtype: datetime64[ns]2 H8 N5 k& P+ k& y
    17 e( g4 v4 i" S6 U+ {5 ?4 _: \
    2
    ! h+ @- {; D8 N$ q: L' B* M! r, T3 D3
    4 s- q, F4 B9 d  o4
    % O  D% g, K6 m, `52 w: ]! h1 a! ?& b9 ~9 C8 T9 P( B
    6
    - i5 H' }. C5 ?0 i; C3 d7: u2 @& n; a* ?3 o6 ]( S& C
    8& r7 W  ~2 X% H+ M: u" \9 O
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    . k8 ]# S; U3 k* `% J  `list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))1 e* K+ \: M2 x8 l

    2 F$ n5 C/ i& n" ]) Q' A[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),1 c/ l% P" [! u, f! Q2 d
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
    / c& g" |( w3 I1 R8 q- j (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
    0 c/ u& X; y1 B. u1
    1 e- h2 D! e  \3 d8 u* |& W2" B* j/ y7 c" O3 O3 o5 `
    3; s" ~! D( F: E
    4, \) ~. N" b/ m+ ^
    5& J$ S6 x" X6 `$ P) ~4 C6 N+ r
    6" G. T8 S. O; ~- z
    参考答案:, k1 s1 U# Q8 K8 J; J6 J; j

    ' C% G0 u7 S- V- K9 ?" \s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
    0 _. R- w1 i6 R* l) ~max_3 = s.nlargest(3).index* ^, z" d- y8 A% [  l
    df.index[max_3.union(max_3-1)]( J! E2 e4 x6 d& n" n9 A0 u

    - Y* U; f% q! K3 V7 G( l; uOut[215]:
    & b8 v; W, r, O( a8 v/ MDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',2 @! a! e) C) H+ ^6 x* R) ?- \
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',
    % @. K7 f1 X  A* v               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
    8 l( n( v; n1 }              dtype='datetime64[ns]', name='Datetime', freq=None)% _( B2 x8 F2 q: U
    15 I/ G5 r& W( R& q
    2$ O0 ]2 m* _/ U% s" y
    3
    3 O% E; z6 D( Y8 _4 J4
    7 z1 V* X3 E  c) r- q0 d5
    9 L' Y0 L% s7 ^3 c: k6: T2 Q% d% ^6 y/ X/ q1 J# b* y
    7
    7 C# n2 ]' [. H: f; W8
    7 J6 F9 t! U) H9
    # [) p' L6 ?) I6 P* c+ Z/ n! V是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。- ^3 s; f) v9 }/ ^0 ?& A/ |& K
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间1 i, M1 g; z; t7 s
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    - T5 u& F5 J. p& j$ T/ Ws.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05), \7 G1 E7 X& N4 ?  A
    : M5 N9 x8 h; g) G7 e2 W
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    5 h% G& n9 y* p$ ~  X7 q' R5 T17 F" w0 E, v$ q8 O; v/ c
    27 X  X% _/ w4 J5 ?; l7 C
    3  l: G/ `+ n0 _/ p
    4% ]* X) P8 W: q) |4 S# U& l& [
    56 t. `" J& W: [
    %pylab inline$ N. U7 u* L5 Q  W, M% S- a3 ]
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)9 Q, q  d& _" u& F8 p, ]  }" g
    plt.xlabel(' Timedelta')
    ; @4 ~4 E1 Z9 E/ w, w( u% J# [- I& rplt.title(" Timedelta of solar")# E  ^7 v: ], G5 R4 P, Z
    1/ w5 o. U; _; b  \2 d; G0 n* r
    22 h+ n7 J0 d( I7 y
    3
    # n7 K& N+ P$ K3 L& g4( P& J+ N1 @# W/ e: ~
    0 t$ y2 T% T* {- B
    + v) R7 @* B' g. D: E
    求如下指标对应的Series:( N9 N- \1 k, X2 f+ ^4 d: |% ~
    温度与辐射量的6小时滑动相关系数! i+ c0 v# V, x  \7 J# P$ L
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    ; w$ r& b! N* v& m: \, l+ X每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量); s3 U9 g( \) C" ^
    df.Radiation.rolling('6H').corr(df.Temperature).tail()
    / i6 i: b5 R& D
    9 d% _$ c! C5 \5 x7 `! b! f: GData7 @- w' L7 v" m, A, y
    2016-12-31 23:35:02    0.416187
      T) a4 l- j% _1 U2016-12-31 23:40:01    0.4165656 s; F9 |& o% z# o
    2016-12-31 23:45:04    0.3285742 J' {. H, e8 i  P, {
    2016-12-31 23:50:03    0.261883
    " F5 b& C( i8 v4 H; r2016-12-31 23:55:01    0.262406  ?+ `+ r" y" t" B. j; z& `/ ?6 [+ _
    dtype: float64
    , B- i( G* Q1 k: |1& X" J/ D" G3 d3 j+ {, D
    2
    & Z, h: i/ K$ e; o3' {" w( p7 ~/ m" [8 V9 Q/ _2 v# o
    4
    & y+ J$ b; O" i1 _/ Y' Q5
    % n/ D3 O; ]4 S$ S8 _# H6& o* A: R" E4 B1 @
    7( N5 p* H- u* T( E6 K  G1 v
    8& j9 @: i; N  p
    9
    5 `0 [# n0 N5 M+ T0 I$ D; d, mdf['Temperature'].resample('6H',offset='3H').mean().head()
    , {8 q9 a7 D, u, p% C, i; k/ f9 C; j2 Y5 w- R
    Data, P/ k/ O+ ^8 q7 Q5 Y' ]5 v0 a
    2016-08-31 21:00:00    51.218750* k4 ]2 {; o% U! L/ ]* Q
    2016-09-01 03:00:00    50.033333
    1 v- Y, F1 F0 N$ n( U* e2016-09-01 09:00:00    59.379310
    $ _# ~# s+ Z, g. y) S5 H$ E  x2016-09-01 15:00:00    57.9843751 {/ a8 v/ f! H: x
    2016-09-01 21:00:00    51.393939; ^: q- t* K- l" O4 u
    Freq: 6H, Name: Temperature, dtype: float643 f5 \; B& I/ M2 e0 F# k
    13 s0 i4 \0 j# f( c) ^
    2
    * c( u% Y8 ]" u5 \! [) P$ d. b3
    ; `( f: }3 K9 G9 I4 B4
    % D& K* k9 ?9 x3 i4 {% O5# {: b! a& Y/ z# E/ E4 |
    6' ^. w5 e5 W" v
    7
    ' {4 a8 q% E8 L% J8( i3 S9 b  _6 B$ ]7 R0 x
    9
    3 a6 k5 x) k1 J2 F/ a* w9 k3 n- |最后一题参考答案:
    $ n+ c* b2 t' N+ u' H# o
    ) g/ A$ q6 T# }! u+ I# 非常慢9 l* g( ]9 \) G5 {  l# e% h9 ~! h
    my_dt = df.index.shift(freq='-6H')
    ( r- D" h, \4 Q+ _int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]/ C1 h/ p7 {/ n1 l
    int_loc = np.array(int_loc).reshape(-1)- B/ e! Y3 d+ i0 b* w
    res = df.Radiation.iloc[int_loc]$ s; w2 \  u. s: u
    res.index = df.index
    ; c0 a3 d4 L  N' S+ }res.tail(3)/ H1 p. F9 S. s8 V, `4 o
    1
    % k) I4 |) D* y8 j24 ?4 W. Z4 D# g1 K, N
    3
    5 V2 P+ d/ ]$ c8 q+ v4" c& O7 N, Q& I$ d4 d
    5( _( U/ h4 j$ {1 C' ~: n1 B
    6: k/ @+ y4 v( {! ?
    7
    ! o4 B& J# g1 H+ U* T# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级' }6 y1 m$ S% M
    target = pd.DataFrame(
    * W& ~* H. l2 f0 p5 [    {& t! h" Y/ g% b5 h% D  Q
            "Time": df.index.shift(freq='-6H'),0 N/ W" [( \# a9 G  r) ]
            "Datetime": df.index,
    ' {4 |( {0 U6 ^8 X0 ~    }0 V4 W! q* y, s. q3 j) I
    )6 P/ y' W' X2 o( r8 T. H
    * [) C" n- v9 Q
    res = pd.merge_asof(6 j6 d4 c% a$ n) ~2 D
        target,3 ?' I8 J( V% }, w
        df.reset_index().rename(columns={"Datetime": "Time"}),* f! S% E7 ?0 W* l8 E
        left_on="Time",  V6 q! G6 J* R7 y! A" {% y, D: B
        right_on="Time",
    + E" ^& j. y8 w0 F) u, j    direction="nearest"
    $ y6 [6 u$ S3 L& y).set_index("Datetime").Radiation
    0 Q8 X7 P, A; _; O7 l1 C/ h- `8 d& F
    & K+ |& G. D' o$ f+ S5 T/ Wres.tail(3)
    # p2 k5 a9 P: T3 p4 N% jOut[224]:
    9 Y! ~: W) `; Q) E& nDatetime
    . j9 p2 ~% T0 k& `5 W, [% L2016-12-31 23:45:04    9.33
    ; @6 z' B! e7 i" K5 i2016-12-31 23:50:03    8.49: K, a$ J* C6 F6 s/ x
    2016-12-31 23:55:01    5.846 R* @# l1 `% [  x0 F
    Name: Radiation, dtype: float64
    5 C5 i4 u/ i/ I- |# h9 j, E/ q+ g' Q6 P7 a4 v$ w
    13 R9 r: J% Y9 l! K$ y; M, Z
    2
    0 e: f% E) i0 v/ k! d7 Z' h: s3
    # M4 E( F( _( F* b! W7 e4  x6 f, a. O* G: H
    5
    9 p, q% M2 t: D  K* W3 z69 R; v' E: o) q; ]5 F
    7. P0 L3 y. |9 ~. U2 n, @; w
    8% F. O$ V, @; \( M9 a+ j) n
    9# M+ ]7 p2 H4 Z1 h( F
    100 N. D) y9 E, q, B& U1 ~
    11& K9 X5 y& P' S4 C6 j
    12& {2 @& a8 C: Y, w: }$ A/ z- f9 d
    13' K0 k. \1 k" R7 }* O1 w
    14; g. g) }& R' ?6 w( y2 g- E
    15) q" [' e; Q, M, x3 S) w: @1 G7 \% @
    16
      y! i4 M( G0 i8 A6 n. n17
    + }3 C- o, T, o18
    6 Q. K, r( [, I$ N* v- ^19
    6 o7 V* E6 j2 X8 ?4 u( U/ c20
      ^) L" t6 b% V3 X5 \1 _21
    1 @7 K$ [" W* x* v22
    $ I  n8 K, t/ I7 W236 F+ M( k4 x8 l; m
    Ex2:水果销量数据集  h2 t5 h7 g) l, O" |6 P
    现有一份2019年每日水果销量记录表:& c! J0 i, c  b: B& r% H

    ( _, V/ J) o  b/ Y  {+ I4 Bdf = pd.read_csv('../data/fruit.csv')
    3 C4 Z7 G! T  J9 Y4 ydf.head(3)
    - V1 A. B3 |) ~  z+ L+ i
    ; A. L3 Q1 C5 ^" G2 POut[131]:
    * `# u$ |7 W6 w0 u% u         Date  Fruit  Sale( |  \; E/ B, }% c
    0  2019-04-18  Peach    153 }3 b5 b6 j* W8 h3 e) ]6 k
    1  2019-12-29  Peach    15
    $ I) W& ~7 B# u0 U  Y$ A! l2  2019-06-05  Peach    19
    8 k# A3 u6 G1 e1 F  I/ q1
    / F4 Q. B- {: d9 q7 x  ]* X% C1 g) W2# k) U! i# y" I
    3# e; U0 M! W6 P( b4 h" D& p2 @" S
    4. r' E0 A- x6 L* m2 K) T( }3 S
    5
    , D, y; o* M6 \1 X+ E6" w5 s7 z+ \+ o/ Q
    7
    % U- b0 b" Q8 Z/ S: o/ w8
    1 {& e4 V9 a. T  M2 z5 y  K统计如下指标:
    - y7 B2 ^' j6 q, {& C每月上半月(15号及之前)与下半月葡萄销量的比值
    - ?0 R6 |  C! F) P每月最后一天的生梨销量总和- _; ~( i( i6 L1 z, y, S5 _
    每月最后一天工作日的生梨销量总和- k' ~1 ]5 k- Y/ i7 m
    每月最后五天的苹果销量均值
    : \3 v: v5 J  b5 A按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。- C. n+ f$ U: {" \
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    3 ^6 Z; M3 o6 C( a; W% i: b0 uimport numpy as np* e! r# f: S( v- s+ l, P7 o
    import pandas as pd
    * o: F$ B9 d; Y$ _1
    - q( h  B  Q( n1 w, ?2
    ) y4 _* p( w, h统计如下指标:
    9 |! R4 Y, F  t0 J; x) r每月上半月(15号及之前)与下半月葡萄销量的比值1 g! X6 e$ A1 N, Z, v* O
    每月最后一天的生梨销量总和4 l* q6 o, t# o5 ?7 q
    每月最后一天工作日的生梨销量总和# x( z  b' t) c# B5 ]
    每月最后五天的苹果销量均值
    3 s9 J$ l' o, y' h* g/ f  I& n& u# 每月上半月(15号及之前)与下半月葡萄销量的比值
    / W; w; n# E8 [  e9 Sdf.Date=pd.to_datetime(df.Date)' \! |7 Z. D& Y
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    $ c& B$ M  c6 X* R- wsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    % Y1 A/ O7 w0 H. E$ I8 Gsale=pd.DataFrame(sale)
    / G4 n! k% d& L  ~; X( `+ i+ Vsale=sale.unstack(1).rename_axis(index={'Date':'Month'},2 ^7 |- Z. h* U& R8 \( x" S$ L1 }
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名9 @+ J' c' I; i5 J7 C
    sale.head() # 每个月上下半月的销量% N, |! H0 f2 V* Z; H6 s6 w) t" e

    9 ~3 I5 I: `5 V  Month        15Days        Sale% A5 e+ ?8 V' s& G; u
    0        1        False        105030 @# \$ l* c2 J( Y9 f
    1        1        True        12341+ W  I2 ~% E) ^' I! q  Q" x, O" z
    2        2        False        10001
    3 Z& I+ J9 A8 b# j6 q3        2        True        10106% a7 k4 i0 t: D9 v
    4        3        False        12814, `* N2 R/ Q: {2 X- U% i- f, G
      V" A1 ]( r3 ^0 ~- j7 M
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序2 _* R* S$ S( Z% Q$ v
    sale.groupby(sale['Month'])['Sale'].agg(0 e# I* C( Y+ G1 b7 U
                    lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    % O! U/ c" ?# ~- C: f' k0 @; Q0 A$ Y" m5 W( M) a  m6 M9 q: e
    Month/ P% J* V% U- n4 h! U8 d
    1     1.1749986 _6 x: d% j$ o
    2     1.010499
    4 w. a0 v1 ~5 a/ n+ ^8 m4 w/ g3     0.776338' y2 U0 e, b8 d; J4 b; O
    4     1.0263455 O; ^  A6 d$ d" _& m& c
    5     0.900534
    & ?$ v& ]: ~# |, O! J: D6     0.980136
    1 x/ u& g) j9 m+ G6 u' J6 H2 ^1 H7     1.350960
    & b$ R% X! j0 W( S2 n& ?. t8     1.091584
    ; ^. V1 q. F) Q4 B  j- q# l9     1.116508
    . h# i% \% z: K; u: ~10    1.020784
    1 w# F! X7 A# s' e( g! E11    1.275911+ {; u3 z& W/ m
    12    0.989662
    8 u8 a, j# V, fName: Sale, dtype: float64
    4 Y9 [: }  A4 ~: P# q( l6 M: L
    : y% l/ O2 s7 [8 m1
    / X6 U6 u% ~' f2% p. {* L, P: e! @* y
    3: `8 r; H' I' i# k
    4
    & D0 H' _" u) ^# q! A7 M5: S  ]7 [% H/ E- L
    6
    4 ~6 _  J- k" @' k( P# D73 i; v5 B+ S6 M9 Y. h. V( v' d7 W, @( U& A
    8. S2 b7 W7 [/ e8 \# t6 T# G& Y
    93 m/ m* Z* {9 \  m- V) }; ]/ z
    10
    5 l' }0 c: Q, t4 O& w' \' G8 n. p11, D+ ^; ?( F7 s  ]. o  f- _# }
    12# i0 y$ z5 T3 R  C& K- ]  m
    131 c) ~6 y7 _/ C
    14
    4 E4 ]% Q: o7 y15
    ( [3 c8 P- Q6 ]8 v3 d162 Z3 O. E7 U( P* ]6 L
    17, Q* Z1 S5 O- E( }+ G8 h
    18
    2 n  `3 u! ~$ k6 |  L- ?& m( Q( d) d19
    ' t  q+ ]% [+ f4 E5 N20. |0 r: C3 v# b* C& b6 b5 w# S
    212 g1 E+ ?+ g$ `3 f$ |
    22
    8 e0 U4 q. P7 j8 i& ]23) N$ y; t9 W$ s) q9 E) `8 M
    24
    # w. d1 l: v( D  N/ B9 L25" Z: E) Y! W$ _. W' U
    262 k, E3 b' r% c2 ~3 V1 |3 C
    27
    ' B, B! l2 x* q# Z( v28
    & U6 u6 I3 ?: j3 n; p3 G$ |3 L29# W+ g! ~6 i! T/ R, J: [' c
    30' S& }% J- O" ]3 ~4 ?+ x; d
    31
    1 w; B& f$ [( r2 a32! _* {. M3 J( [/ ~  O$ N' e0 S, C1 w
    33
    3 j8 p/ r( I  f, \3 f6 \34: \6 N( E0 |. O; r7 l
    # 每月最后一天的生梨销量总和- N' f3 ], r5 v" W" X; r
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    ; h! D' u$ A5 f- a/ I! n
    + `  E( I5 Z2 {3 V* B8 nDate" U! S, L8 |7 u# \9 e/ o- R. M
    2019-01-31    847: @: h# L. U6 c3 a& `
    2019-02-28    7742 N3 F" j' v" y  i7 |
    2019-03-31    761
    + r; |9 p1 e$ c" C; ^# \4 \2019-04-30    648
    3 t7 Y2 X# a" {* x; b5 |$ c2019-05-31    616
    2 }( \# T* J9 E5 a: V" N1# h/ ?) Y1 N( D/ \" m
    2
    0 f: ]- Z; h9 ?9 t- S" S9 k33 G6 r# [$ L* c3 k9 V2 q
    4$ z' Y# _. [+ @" v, x: W0 M
    5
    9 T$ M4 n2 |$ Z5 E5 a) P6
    7 o1 J4 G5 D, }) y2 r7/ M: Y6 v) h' {
    8
    7 u4 d4 m" e0 |  M- ]. }5 {, }$ }9
    " Z; d# S  I5 q# 每月最后一天工作日的生梨销量总和
    ) {" i) k- I, @7 kls=df.Date+pd.offsets.BMonthEnd()# d( K+ C- L3 \$ Z; m; Y8 k+ C% U: F
    my_filter=pd.to_datetime(ls.unique())
    6 D$ o! D$ _7 _/ n9 s8 Z1 b9 |  rdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()  A3 q" {  n; y6 @: P, r

    & L3 I$ w! L+ B) z1 ~Date0 z# D; s) r/ k5 V: s
    2019-01-31     847
    - Z7 j! y( h/ O6 o; \! A2019-02-28     774% t/ J$ c1 I6 b. C7 ?" l/ m. F
    2019-03-29     510- k" ^0 |6 W( k; L3 I- d* M% Z
    2019-04-30     648
    ! e% N: r5 O% S6 H2019-05-31     6161 b& v5 D" `; W$ _. B
    1' o3 v) [/ F3 x1 h4 Z
    2
    9 `& J& y9 l1 z3 C8 U  n8 b3
    3 p/ b  A+ c/ k2 a  q! S4- ]# i0 A, Y- x- U2 A# w
    5
    6 l) C. o+ h3 p  h5 w  p& W6 h$ a6
    ! S9 t8 P' U. L3 T% V1 R7
    4 \, E! V) r6 w4 w1 K. \1 K8  C4 K, k- c( H- W, C9 B
    9- U% m* v: V* Y( I8 f4 n# a- w$ s
    10; v. K7 H" R& A& {: ~
    11
    * I4 ]+ k% ^2 d& w! V# 每月最后五天的苹果销量均值
    $ }# u8 G& D; O, v! O0 Vstart, end = '2019-01-01', '2019-12-31'2 x6 E; V. Z  P# ^' q& [! M
    end = pd.date_range(start, end, freq='M')' b' E4 \) l! G' k* B; I
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    / @! T8 c, u4 y+ _6 O: \( o1 \! I# O/ u) k7 ]( `
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    # C; B6 @' D9 X' ^3 `! C  Ftd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
      w, d0 S% ^6 I9 ]end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
      q8 \7 k2 B( m+ ?, k' @& L1 U+ l: s& l1 N. ~' k" I9 i8 v6 k
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    8 Y8 d3 P6 q  @( e! [apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()) E+ b: N  j1 g7 r- K/ V

    ! t" d: H+ Y8 Q  N3 _$ ~5 W4 J/ ZDate- ]! y) U! ~: [) y' ]! k$ }
    1     65.313725  U; X( K+ T% {& Z8 @( P4 u
    2     54.061538# p4 l( W1 P( D3 e$ K( y% K$ q
    3     59.325581! M$ Y9 m2 \/ D# A/ E4 H
    4     65.795455
    $ A8 ^7 A6 R( W" t5     57.465116
    & V' i" E$ v# k0 y8 B2 o/ V* ?$ B/ Q
    - C: ?" M6 W, w6 Z3 h1 o1
    / b5 E6 P4 B# A7 M+ w: f6 I2' E& I  A% _; s% \2 C. N# R6 y
    3
    4 H8 l, o# b, P" U4
    $ [; y+ h1 c; x8 E- i% S5
    $ C% a) e& ^0 N: i/ G6
    + g( Q( [/ y: F% m7' T; B7 j% ~4 A
    8& q' t% r8 m1 g) P
    9; c: z" V% }5 H" ]
    10
    ) }9 i- \, V/ Y" G% r; Z. r11
    ; l6 f! ~4 c3 z) _9 o! P& z! `12
    ' z; j4 [) ?5 X. i13: X% b' D' b( p* ^* ]' P
    14/ h! M1 Y0 O. s! S! F
    15
    & b& e; w7 J; k/ [4 s6 r+ @16
    & _# E6 v/ f5 V/ p) ~8 N+ D177 o/ j$ R5 s. x8 s
    18" X. s9 D& r% s' a; @
    # 参考答案:
      v/ t; u+ |3 \$ Mtarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(  a0 y/ {1 {' |" M
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True), r% l" W. g0 j$ T% K4 V

    & U; C0 w2 h3 W4 k) H8 e" _res = df.set_index('Date').loc[target_dt].reset_index($ @8 z; @8 r* h# U& |
                ).query("Fruit == 'Apple'")3 G" |( u/ Y' {7 g* ^  ]2 V3 r+ Z8 s

    # Q3 C+ B' k3 d3 h' q) D5 Sres = res.groupby(res.Date.dt.month)['Sale'].mean(: R+ b5 S8 U' G9 d8 K- W0 ^
                ).rename_axis('Month')
    ' ]; g2 U7 b$ V2 q
    9 v" Q* K. o4 Z4 L6 P. f
    ) [2 k7 U$ V. M+ {4 s, j7 t) Ires.head()& y" w1 t! r# u  N; Z
    Out[236]: 8 t" \. \7 ^& C& I( O
    Month6 U$ u6 q3 O! M# R" |- z" g
    1    65.313725
    - I8 E5 `) R0 p- `8 l6 i2    54.061538; c8 E; E! P+ @
    3    59.3255812 `" o* w. B8 b" X0 f
    4    65.795455
    8 ~' ^7 i- Z: {, D! _! }5    57.465116" T6 f2 Y. v" n* y
    Name: Sale, dtype: float64
    / V& X' ]6 n# V+ j/ H0 E2 R" i& b0 W, x( P7 m* q  j( F
    1
    1 W: s: j; n* X# O3 w9 c( T2
    " M: @5 q1 t' q39 {! W) t0 x7 g
    44 J4 F& z# E) V: |1 X: y1 E
    55 ~0 a( T9 ]  U# A% W
    6+ U9 n- e2 ^8 O# h# G: n9 O
    7
    : p1 ]1 q! H# d9 ?9 ]8 X8 ^8. K+ G* n& L7 ?3 G- W. m
    9
    , ~' E; f  @% R. ?% Q( \! T100 R# i: U% W0 H5 L) O
    11
    . O4 L) x9 k4 P/ ^12
    6 a+ k2 {% h" o4 C* Z9 E13
    # O1 c# r1 M0 F  w( |14: C: o, \2 U6 ~
    15
    3 d$ ?0 U" R# v( L  p$ `8 ^16
      p' P1 _% X* d) j, q2 a. @17
    : c, D$ [+ i5 H" a* T18
    . S/ D( Y8 I  Y, n, I19
    0 E; ?4 z* o; F! p2 t( P1 J20* M6 h6 J$ A8 h
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    % E) Z5 M4 n4 D9 s# Presult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.3 p4 b' a4 x& k8 V4 f# b  m+ ?
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 - _9 W9 l) A9 `0 H( r( ~- Z: R
                                           
    4 i4 f4 C( x( d0 l) Kresult=result.unstack(1).rename_axis(index={'Date':'Month'},
    / D3 J. K7 ?7 h2 t: ?- r# v+ V                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
    2 V  q1 a' I! Fresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)8 p7 C7 m1 @; N1 X+ m2 ^
    result.head() # 索引名有空再改吧6 E- U8 n! X) ~+ a: P. _% S* @

    4 I8 k* w0 c' t  r' r; M# Q          Week        0        1        2        3        4        5        6
    7 A$ y% m  E1 F$ a) P$ k! n7 l  ^Fruit Month                                                       
    ' N% l) N3 m6 tApple        1        46        50        50        45        32        42        23
    7 r7 B( C0 c; C1 p* x# s( }9 W1 y; HBanana        1        27        29        24        42        36        24        35
    , p% P7 v& D* }8 w5 D2 a* JGrape        1        42        75        53        63        36        57        46
    * Q" E* j2 D4 G& Y8 jPeach        1        67        78        73        88        59        49        72- m7 p: v4 s9 L& O* {; A
    Pear        1        39        69        51        54        48        36        40
    . a' I/ `1 t+ k: T- \6 m) \* F1+ F3 S2 Q( `0 Z2 g, Y
    2
      E  Q) Q- W6 a7 i$ o3
    ; o) z( P. |! r3 f, i! c4
    ' k: W- R! l0 L; d/ S5 V: x5
    ) [$ b" ?$ x3 w, i5 W9 n69 J# ?- O3 Q( y* r: {! g! Y
    7
    , h. E8 o1 h! D! Y81 o4 g8 U/ R% A
    9
    5 M" C; X& f  z' Y1 k' M. |/ a3 H10# R- m+ t7 `+ e# Z: N9 U8 n7 v  ]
    11- P) u& a: L- y- D8 O
    12
    % a; l  Q0 R6 \/ _2 X5 A( ?13
    * |3 n/ q) f4 Q14
    4 Z, Q2 B# C; N: E15
    + k- |* v+ \# V+ r按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    3 {) _$ r$ @' b5 O4 G# 工作日苹果销量按日期排序2 V- N( m( J& t& T; _
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()4 _  j2 `" n- S) Q4 }+ `7 d) P" v
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
    4 F6 q( m1 l0 t( t2 ^2 \select_bday.head()& G6 n4 o1 Y7 y, Q3 O

    8 N' u& [3 J. v/ d& y9 k! y. D# HDate$ Q! g8 A) |4 L$ Z
    2019-01-01    189/ r4 t; T/ L6 s; g( N
    2019-01-02    482
    . f' P# m# o1 w* W+ t2019-01-03    8904 |  G  O" q" d' x* Z( T" _7 P
    2019-01-04    550) F# N. g# u0 }8 A* z8 C
    2019-01-07    4949 G/ a0 |: a" o( P7 f( q, X% j
    4 N( [) W' b1 Q% g7 i
    # 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。6 r6 Y" L  V$ h9 @7 R1 n
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()5 h) d% t* E$ s6 \
    ' V# B2 ^* S7 Q
    Date
    & p+ t8 G. m: U- ~/ ]- u7 t) F2019-01-01    189.000000
    & c* a( l& `  `2 f, b8 D* p+ Z2019-01-02    335.500000  t: t" b  i" a4 G+ x4 h
    2019-01-03    520.3333331 `* R* s' n% T+ }% n' x  Z
    2019-01-04    527.750000% L7 ]" ^" ]) V+ s( G
    2019-01-05    527.750000
    3 k& W) t, [' A* ~/ c+ C+ {
    / `: b) N, ~/ Y7 u! e————————————————
    ' F1 j7 S( k" o. ~" N: Y3 i# ^版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    4 e7 B$ M( @( ]4 @( }8 f4 q原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    * o8 z' d, j) E: t
    , Z% X5 y- \: p+ b" F' w
    % [$ v$ M! H/ ]6 b2 z( R: v
    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-6-11 02:23 , Processed in 0.414567 second(s), 51 queries .

    回顶部