QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2277|回复: 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

    6 E2 |$ U8 A! |) u
    7 @. k/ O0 @' ?% s( D( z; W
    " F* m" y5 A+ ~2 U. T3 A4 N) j文章目录
    : y' }# m5 P  j# B# `第八章 文本数据
    3 \- h% {5 o# X9 L& |* {8.1 str对象4 i3 L) q1 T/ D3 ?& ^; d0 L
    8.1.1 str对象的设计意图
    , q5 a" v0 Y. S% {% p8.1.3 string类型/ n: ^! f+ u) ]1 o
    8.2 正则表达式基础
    ' ~1 i- w: @3 I2 y- \8.2.1 . 一般字符的匹配' R3 h7 \/ @7 t: |" H1 \7 o
    8.2.2 元字符基础
    6 J- \2 k) L/ v, }8.2.3 简写字符集
    , T7 S5 r3 h0 ?2 x. m0 H( U8.3 文本处理的五类操作
    & ^, g+ D( T! L8 U8.3.1 `str.split `拆分& ^1 |1 K" S9 f6 M! ?
    8.3.2 `str.join` 或 `str.cat `合并
    % [4 L% d- ~0 O2 i' W) ]% j4 j8.3.3 匹配
    7 h- D) J( h5 {5 [7 A3 Q8.3.5 提取) C9 M& u5 H+ |5 i
    8.4、常用字符串函数
    & Z  k1 P7 P  y% M2 j" z, j% o8.4.1 字母型函数
    * E2 }% L; S) ~1 H+ Z- I  H- N- R8.4.2 数值型函数& @4 E9 U. B) B" X
    8.4.3 统计型函数
    4 L" l5 ]1 O! H' \9 s  W+ `* m8.4.4 格式型函数8 G8 H" T4 F1 K# `4 o' U
    8.5 练习
    + Z) w9 Z& _( ~' ?* `, I& t5 PEx1:房屋信息数据集
    0 ^6 r( b! A# T; Q% e+ IEx2:《权力的游戏》剧本数据集& W" u. @7 E( s; ^% o
    第九章 分类数据
    " g# J8 s( e: _6 f/ ~; @2 B9.1 cat对象
    % f. ?6 m" \9 M5 I3 o) V9.1.1 cat对象的属性8 q" _" [  l/ ]
    9.1.2 类别的增加、删除和修改- J5 _  t: h0 ]) m, j' E* g) |3 ~3 f
    9.2 有序分类; d/ T7 p$ z- z, m
    9.2.1 序的建立
    * g; M4 E4 _1 }& n9.2.2 排序和比较
    5 `+ n% h8 y1 ]9 e- B' T9.3 区间类别* l$ V* w, A5 c1 i
    9.3.1 利用cut和qcut进行区间构造( c4 q5 z5 g3 w7 [1 z. j
    9.3.2 一般区间的构造7 c, ?6 S+ ?& t# s
    9.3.3 区间的属性与方法$ v8 D- y2 @! H, {/ ]# g
    9.4 练习& g  F. G$ D4 z% F
    Ex1: 统计未出现的类别3 e7 l" W+ P0 P# ?) q6 s; ], m2 G
    Ex2: 钻石数据集" C# S& j5 ^5 y1 [7 L& E0 E+ {
    第十章 时序数据
    + A! E* u- {) S2 i4 _  b! B6 o10.1 时序中的基本对象
    ' k9 v, o: V- [/ D10.2 时间戳
    3 r- a- z0 r( U2 o10.2.1 Timestamp的构造与属性' b2 E) U* |& o( Q1 u
    10.2.2 Datetime序列的生成! }# O6 A8 L0 Y5 S/ P  {2 v: e
    10.2.3 dt对象
    1 Z' U! F* D, Q9 z10.2.4 时间戳的切片与索引
    : J" x- d4 a7 o- i1 ?- ^) [10.3 时间差
    8 t* ?8 o3 i+ G10.3.1 Timedelta的生成. o5 l0 l4 o5 ^/ }+ s
    10.2.2 Timedelta的运算, W4 Q* f9 K. V# @% b8 `( V
    10.4 日期偏置/ S) U% n3 m1 m3 A' A$ q
    10.4.1 Offset对象
    6 M8 F1 J7 L' n10.4.2 偏置字符串9 S8 L: M. ~  y( C! m' r
    10.5、时序中的滑窗与分组
    * y. z% l1 D; r8 ?) ~( \) G" i/ X" Y$ P! a10.5.1 滑动窗口
    ! b8 X3 B$ n# E$ R& L. M10.5.2 重采样9 s2 F; w3 U, A( Q5 b" _
    10.6 练习
    ' n' K, H1 }4 |' J5 GEx1:太阳辐射数据集: V5 ^" M3 S) E( z" o& p3 n6 y
    Ex2:水果销量数据集
    % v5 }/ c: F. u8 D! t; r2 U$ q% E. z  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    ) X; b8 Q8 B& _5 _1 l5 m% V传送门:
    # P, z2 D7 k+ E1 P8 C6 }3 E# }. \1 ?/ H2 p
    datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)( f  j! [* V$ d5 l. l# R6 w
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)3 y0 M5 ^' j5 C; h0 n- n
    第八章 文本数据
    + }8 N& F( {2 Y8.1 str对象
    * r8 w4 C) n, n$ Q. Y* Y8.1.1 str对象的设计意图$ K& w' a& \! l9 F* f
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:- r$ V' d3 Z5 f& x, o# I6 E/ t* X

    ) @4 k# a; b" U( g. ovar = 'abcd'% G, d8 v, j5 e
    str.upper(var) # Python内置str模块
    $ v; k; d9 Z* l! O: p* @4 bOut[4]: 'ABCD'/ P3 p3 K' w( i( j3 z3 b- l
    0 G1 p. o% z# ~8 E% M: w
    s = pd.Series(['abcd', 'efg', 'hi'])- T6 ?  d$ U) ^4 N  T1 J+ E
    2 w; Y" f1 `% q7 R$ j, V" k
    s.str
    8 `8 P, |& E5 m5 ]8 |3 Q1 rOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>, x$ h" a! l2 d0 m2 X

    . S6 |' i; \7 _- Y7 T! y' D0 ?; es.str.upper() # pandas中str对象上的upper方法, F' |( ?/ d3 ~4 o" ^% ~
    Out[7]:
    2 z- L) V4 f* L0    ABCD
    ) R# f' [1 O0 ]+ t9 `+ C  J% _1     EFG
    $ b9 N* @5 V  }1 f- j9 q2      HI* O* G( z2 L0 M4 j# R- N
    dtype: object
    $ B' ^. b& ]2 b) w8 r5 y( H- v1
    9 K+ T* d% w, y5 d( R- I. t25 W- \9 X" Q7 _5 H1 a$ c' G  o! U
    3
    ' f" ?9 Q6 U. h: d4
    7 y  w& ?/ w, F" d5+ Z6 j* v/ Y2 I/ {
    65 U- o$ o, m- _
    79 t( J. {3 m4 S) {
    8
    1 S* w5 j. _: K% A( `7 X9! ~- l4 w3 q( o3 n
    10
    / U) \6 W1 _$ J1 p1 \11" P: Z* Q: n% F. z' I. [- V0 |
    121 _6 L7 f! _, M3 G) P
    13
    4 E# V1 w$ _2 d0 s0 O14
    + {9 F4 W8 C4 \& Y. r" |9 ?15+ ^- F9 g- @$ d- }) J) k1 p
    8.1.2 []索引器
    . i) a  T* ~0 x* r8 e  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    / Z( f& V9 ], F7 T% w. }5 m/ R  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    ) n' ^1 ~& N+ s: @  A5 t1 p+ L; o
    s.str[0]# c+ E3 z9 P4 U$ }* S! h1 T
    Out[10]: 9 ^+ s3 ?9 d" n& s. h
    0    a
    , _# z/ s+ i7 U* F! Y  X" Q1    e& T7 o" N! H0 H( _" |
    2    h9 r3 f+ r8 F: D% t
    dtype: object: S7 l6 X' ?1 ^

    / @8 N, _. \& ~4 ys.str[-1: 0: -2]  e6 j3 e; C8 q2 y) t. Y  F
    Out[11]:
    3 h* |, d, y, M' T2 D0 k0    db
    / I8 H; {+ a9 K/ j% I0 S1     g
    7 |/ `+ V3 b% L, `2     i. _2 ?8 P$ L% I& {+ h4 A8 t3 m1 \& U
    dtype: object
    1 r1 G6 X. }: Y- Y9 R' D
    ; x" `6 |# M/ }" ?9 Hs.str[2]
    3 X2 Z! w" a1 B+ IOut[12]: ! [! L* f7 i( q6 r
    0      c
    % o- R- T, u8 [" Y4 h: {/ d1      g" N8 K5 z" Q" a& O, `( H4 A$ z
    2    NaN6 {2 L. K: v* N6 A
    dtype: object
    2 w: d5 E, e4 Y$ y( ]! W. \) N8 n1 {, L, }5 D( K
    1+ J1 t: H  |+ W  S, B
    2' H- y- z1 Y% Z8 o" h
    38 D9 n& [5 z/ ]
    40 a9 y5 |, ~7 r; Y+ L3 `
    5
    . L5 h2 m. u+ `  j: ]6" \' W% h; Q) F4 F$ O
    75 V5 _6 O, F# i1 Q7 f
    8; N: b" q! g6 X( z. t
    9
    ) u8 s- X/ k" e3 q4 \! i  {3 ?# _109 r" [1 s. q1 O* X
    11
    4 o9 g$ R$ P- a4 ^$ G% E12% ~% R3 z1 L( \; H* E8 K3 l
    130 L% M2 f# ~$ C9 }  d
    14
    3 {; |/ [. z( n3 o# o: w15
    4 S3 Z! E" P; m162 ]! S; X9 r+ N! `% y+ {
    17
    . T( z# G% ~5 ~* T18
    3 E8 h( n" D4 i9 I3 ~( H% V& k198 R  `4 G+ n! I* N
    20
    / g8 p+ O7 \! B( x, D0 Iimport numpy as np- w7 v9 y( m' t5 T
    import pandas as pd+ {5 v5 e8 t' X* Z; Q4 `
    + D  _5 N2 q- _* A1 f
    s = pd.Series(['abcd', 'efg', 'hi'])* V) d5 }! m' _. k, B( A
    s.str[0]
    " u! ?4 Y0 t9 m& k0 x3 t6 w. E1
    / s1 ]7 ~: A) ^5 Z" q4 Z2
    , E, |- e- r+ {0 r" h. o3  i* h, U  B4 I+ j1 \
    4. ~* |, J8 p0 J4 v
    5( z5 U  V% O' O, ]
    0    a/ b+ T% |+ w( {6 n8 P6 j8 O. W4 t
    1    e8 o+ J9 d% t1 n3 x* v0 N& @
    2    h5 q4 a9 h* \/ d  n+ _! R' T! T
    dtype: object- }& k" \' ]: Y$ v# b
    1& @, ?2 e  L  |/ e8 f
    22 t  [5 j5 }0 u
    3& N5 Q" K) m  |& v& V& P% r
    4
    . x" ]& N8 ]! {! l8 x8.1.3 string类型, u5 J& _6 h# M# M
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。' m$ q/ Y3 D, n: }& i% v
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:' ?2 C( _. F7 G8 X3 e. f- f7 F

    1 Y* s' Q: P( i$ ?. j- a$ }7 m/ B二者对于某些对象的 str 序列化方法不同。7 b9 b8 f' k; k
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:) M+ P3 y: [2 ?0 x
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    0 u/ x5 o! y9 A7 W2 O+ B: js4 n1 K/ w5 l$ ~$ G
    1# Q/ Z0 S9 W8 f6 n
    2+ X9 D  V& Q% d2 f
    0    {1: 'temp_1', 2: 'temp_2'}
    4 d3 j* \$ _" q5 x7 q* K1                        [a, b]
    : Q# _* k' T7 H) F$ ^; c2                           0.5/ W' |6 s. W$ z# T3 d5 `
    3                     my_string
    ! G, [( Y0 Y$ k' w/ R. cdtype: object/ [/ w# O1 B' S6 ?- M0 W" T
    1
    $ M& U; R% |0 |% v0 x2
    ) W# r- A8 X  ?* T, U% p3
    2 M# Q, x  u9 l40 Q3 H) Y5 U) ?+ m! R
    55 n4 {2 r7 {( D& B( Y# `6 q
    s.str[1] # 对每个元素取[1]的操作
    * r! X+ Y6 G; T: ^$ {3 h7 q' N1, `8 o* E: @+ ~& F
    0    temp_1
    9 e: y" |* u& E- \" u6 q5 u1         b
    - a& \1 \0 R# n8 ^! Y2       NaN
    , I3 [+ u( d# a9 O" k$ Q/ i3         y# p/ G. I6 h) P3 `0 o1 s
    dtype: object
    + Q8 i9 w# h9 T1 F1 X1& ]# \+ x. Z+ B+ |
    2
    8 o. G" R: X; w1 K8 F8 [/ J( R/ N3  ~( i0 r* M0 f" C6 K& _
    4
    1 e3 W" L7 x: P3 B9 R, V. d5, I- l3 E3 ~! C* E, i
    s.astype('string').str[1]
    3 v, F5 d! H7 h9 A+ d3 s- L1; V7 c7 T" m) R2 C2 y8 J
    0    1
    ( ?% \' N! w" k3 r. n! @- E& E1    '
    5 n& ^/ {! B1 p2    .
    ' ]  w" O, h6 X* `! \3    y
    . o. F# \# S$ Y( i4 ndtype: string
    2 ^6 B* h! {# G" P( ]; U1
    ) |! n  N( W4 I' |, `  J. [2
    ' O4 `! @# w# m* h5 D' O9 |+ W0 E. Z3
    1 k% P) ~' ?: J" ~) w4: `' H5 C  P$ V1 ]! p" U
    5
    ) ?7 N6 i+ y- u, @& J( O除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    ; h1 A8 z  h' X! }: [0 i  x; s" L8 p; l; m/ P1 x) M# F
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
      O% W9 h. M, V! Bstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。; O  k8 @  s6 U! L
    string 类型是 Nullable 类型,但 object 不是
    & M- F  O6 w$ V7 `  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。5 a/ s6 G  m5 R* z$ ]$ U1 |
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
    3 T. P3 k7 _, \2 Ls = pd.Series(['a'])8 e8 z: g# Q% a/ h, F  ?0 p
    4 u$ a. L2 m! S* y0 i7 }; Y( m
    s.str.len()
    : F9 G/ b' W/ Y( s6 WOut[17]:
    " C2 f* D, V' V4 G5 Y0    12 l" _1 ?1 T$ B# j3 f
    dtype: int64* t8 b) F2 _8 k/ \' X& X
    * J5 Y4 }6 _' ^
    s.astype('string').str.len()% W% |. u: a$ j
    Out[18]:
    % N4 p3 |9 \2 e: G5 e0    1
    # r; J' q4 F5 L, E* Q) R, h2 Vdtype: Int64
    % F) n4 a/ _$ Y& Y3 l5 W# `; o8 ~- ]( M3 L4 Y7 T7 f, G
    s == 'a'
    * O0 Y: Q$ }7 b: h- \1 GOut[19]:
    $ F- y; h) b. u! U) |" Q0    True) T+ `5 |& }' T- N3 m
    dtype: bool! W# x: \- V' y) k

    ( C3 J, t; _, c9 c, \4 B' Js.astype('string') == 'a') b( V; ^+ n! k" [
    Out[20]:
    ; ~; F2 E9 G1 M! R1 D& h+ S, J. F3 c) z0    True! R6 x3 y: v$ G& h' j) C
    dtype: boolean' n  U+ J4 ]. v6 K! S* ]
    3 p: m0 b: s2 S* t: ]
    s = pd.Series(['a', np.nan]) # 带有缺失值
    % p0 }5 H) R* U5 f7 x
    . r; e$ n; Q+ d; Q; E( Gs.str.len()
    & L$ h0 l; I0 p. i# b  s7 O: kOut[22]: 8 X* x2 l" k1 C1 c! M' ]) V
    0    1.09 u/ u  R. p( ]
    1    NaN
    ( N4 p( B$ {* ^2 rdtype: float64$ y2 A" w! p( K! b8 }- M: z; b

    ( k0 C# i% A* i1 J( f: Gs.astype('string').str.len(). u- s+ Q& b& A/ ^* ~7 i; X
    Out[23]: ' u. d& s2 o& E- Q# Y6 A
    0       1+ e- Z% ~, Z4 Y( G
    1    <NA>4 Z! o8 B5 S/ e5 ?; y
    dtype: Int64* h; J  x( e% D

    / l3 L  Z5 i. j* T. p" Ms == 'a'
    9 K* ^) L! w  x9 E0 yOut[24]: $ e" \- v0 w1 Y) j  X+ \7 ~
    0     True
    5 l( A7 J* h1 A3 ~( U7 t1    False' @/ X; X7 U/ Y0 ~( K2 r; p
    dtype: bool/ s* U# ?- I9 X& H+ o4 U

    ) A% g0 t! h: ^  n6 }  zs.astype('string') == 'a'
    ! J4 y6 Z* Q7 i1 wOut[25]:
    $ n) X2 \% B) O3 H5 V* ]# ~) J0    True
    , Z$ V! U- F0 |3 E# s% J1    <NA>
    1 K2 Y- ^, b, V/ P3 G3 y$ m3 S$ |% udtype: boolean
    8 [2 G# Q- l( c0 _  @; I9 d
    8 T* ?3 V( P  j% R! i5 K1
    ; x. n  m; S7 X4 |, E2. z. ?8 V4 j9 U  c  M% ~
    3, B4 Q. g0 ^4 W" {& z
    42 u. t: {4 n, s( P3 c5 I7 W0 ]
    5; [) i- K/ |1 i, Z* A% T4 C/ ~
    6
    ( H1 v, N7 K, `, T8 p" [; s( D7
    $ x* F- C# V9 e7 W8 s8
      X1 d0 M" E$ t9 E' O9! A9 `; _% M+ p& V5 l- Z) {3 Z) M6 V$ y. R
    10
    7 B. f5 E: X- e6 |8 r7 D( n11. \* H0 R8 n6 {3 A* ^
    12
    5 I& T+ I# |7 `6 ]7 a- \! G13  W9 q, r/ O7 s9 ?
    14
    ! W/ K0 b+ x. e. K0 k/ J) T9 R  h152 t+ W; R/ {0 @3 z: H
    165 X4 Q( Q) {5 I+ P* k4 n
    17
    & W3 I. b- V5 o18
    / d& P% V: z- ~" s$ S5 z0 h! B19  |+ I8 \2 i. U9 ^0 s$ |
    20
    + H0 S- s* N- u' i$ {215 Z9 o7 q! H2 \3 |
    22+ I7 L, T6 k, r0 t5 Y- |" N7 P
    234 ^: m4 j  R/ f- p. t
    24. e# m+ q3 U: c
    25
    ' L+ J8 y" O7 \, x0 i6 ^26
    + v# g6 O4 W( i  G' j; x27! I# o% D" j0 ?) ^, p1 ?) @
    28
    9 w# m" I" r( D2 \299 e! L( f, U( H2 q3 Z
    30' X8 W5 }6 L0 Q
    31: H* \/ P! L1 n9 R$ f& f+ f! e
    321 E3 F# t- u1 q* p5 ^
    33
    6 T" t3 K3 P  V/ X34
    : `4 F' J5 m# `1 s+ n355 o  N  Z" ?2 ]* f! p, w
    36- L; }* ?) g$ t  Y# E
    37$ B2 L! ?$ \3 ]  y/ S
    38
    8 Y( m$ V: D0 d1 {392 P# T6 ^% A9 B6 Q5 B5 A% a" l
    40
    ! v1 H' Y# a3 f7 K0 c3 k41
    3 x0 t3 O/ F0 I' A  J4 _" `42
    7 n% y* l) b. Q/ t43
    0 R( v( K7 ~% e! Y6 ^+ V3 S1 M44
    : S/ i" D. D3 @4 J9 X$ C3 v6 }; m45' \, s. K, x; F: a% l" F0 F
    46$ T& M0 ?+ W& p2 i% Q" R
    47
    * c" M) O' L' d  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    $ g( ?( E7 C; x! {
    % E. q  `! e, f$ Z3 @/ ?8 Ls = pd.Series([12, 345, 6789])
    , e9 d' S- k; m; b; E( v3 g3 @+ v1 @
    s.astype('string').str[1]5 {! G& \' I# C
    Out[27]:
    ' q" F' p: E0 S1 B4 [$ c0    2
    ( ?" P' T4 u4 h8 Q- E, n4 \- T1    4
    $ h4 K7 R6 {: O/ X6 C7 h3 F; n2    7
    " G$ V7 u) ~) L) G7 Ldtype: string
    5 S$ \# g: o$ k* B9 D- [, f13 v, ^( z; P+ @" _8 ^
    2" s# Z( Z1 S: |
    3/ A( u' v9 y) ?( c
    4; a9 d3 j% S* t  v& S6 S
    50 g+ W- W1 E! t( t, p. Q
    6
    0 G: d" Z- [' E- n3 V0 J' D70 G& n6 v# Q, ~$ ~
    83 n* N6 s0 b1 b  T) l1 B
    8.2 正则表达式基础8 h, l- Q2 e3 {+ Y6 }/ n
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
    3 ?) c9 H) \! ]2 O/ {' o0 W( ^7 w7 r. l' `* \1 x
    8.2.1 . 一般字符的匹配. c" D$ q* z; d% R- j
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    # w6 p: S4 G7 N* x5 O) y: {5 W8 \  x4 D- S+ u5 a# ]
    import re) n& {1 f  o0 u& n4 I* \; v) V
    & o! c/ W; Q% R5 {2 q  ^9 S) C
    re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
      H: E7 c1 u! F+ y+ ZOut[29]: ['Apple', 'Apple']
      ^7 D  C1 M2 k5 A% `0 Y7 C1
    ; v0 c( @8 \" n7 }& h  P2
    6 l1 r; w8 q! ?0 W7 B" ?3
    2 A3 h+ d; w: |4 j4
    4 p' {- \: W/ S0 V. q% N7 K5 V8.2.2 元字符基础+ s& w& E7 x4 M+ K1 w
    元字符        描述4 t7 ^, ~9 E6 Z% [4 d+ c# S: d
    .        匹配除换行符以外的任意字符2 L& ^5 T9 I( v6 N8 D
    [ ]        字符类,匹配方括号中包含的任意字符7 A! Y. w& `0 Y7 v
    [^ ]        否定字符类,匹配方括号中不包含的任意字符
    , R3 C% r1 s. q*        匹配前面的子表达式零次或多次7 z0 ?+ Q- W5 x9 x7 ^  A( t8 [
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字& z5 q  [' E. a/ k7 S
    ?        匹配前面的子表达式零次或一次,非贪婪方式
    ; w7 S: m4 r8 B. @, S{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    ( Y$ @1 h' E; }  o, g- P, {(xyz)        字符组,按照确切的顺序匹配字符xyz" M" j# p- S# {- w
    |        分支结构,匹配符号之前的字符或后面的字符8 V+ D0 a# Z4 H7 X
    \        转义符,它可以还原元字符原来的含义
    . i/ T6 p$ _4 V$ s8 i^        匹配行的开始
    + U) U$ v  Q6 R/ n: D9 g$        匹配行的结束7 x" _: H9 B8 D: ^3 l" ~7 n9 G. a& W
    import re, G! U2 Z7 y" b# M
    re.findall(r'.', 'abc')+ p4 V/ u" u0 K% |! q- e! _9 b
    Out[30]: ['a', 'b', 'c']" i- }8 \7 v* ]+ A* F8 r
    2 `. y. n  f0 c: w. `2 [
    re.findall(r'[ac]', 'abc') # []中有的子串都匹配
    - m# ?" [- {- m& ?/ y9 hOut[31]: ['a', 'c']- V, R5 S4 Y2 j. r

    5 K. U" V1 @! h8 a$ Q7 r  b- ]re.findall(r'[^ac]', 'abc')
    1 c3 Y5 r7 b- L! s7 |Out[32]: ['b']
    8 M9 `# J7 T$ Y/ h! R/ ^* b
    0 J+ g# C' |7 H6 ^5 n( |re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次" X1 J1 C  J4 K, h* [
    Out[33]: ['aa', 'aa', 'bb', 'bb']! d- F' k$ _8 k" i3 c4 P" {" k0 H

    6 x. V; k7 O3 X2 J% V& Qre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串* {' M' J5 z- {8 m5 [
    Out[34]: ['ca', 'bbc', 'bbc'], w# l6 ~. N6 A5 h  z: O

    7 q3 q7 u3 D9 \" w' O. i4 m# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    7 r( f, X  F, |7 n$ u! d"""
    9 F6 p2 G& S  E8 P( N9 R1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
      O# ~0 g5 l# L$ `; P( z2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边$ W3 r4 O% Q% F0 H
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,7 ]1 |2 t! v+ Z2 c: R4 T7 c3 i
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    ' J) D+ ~0 Q7 ], p8 q""": F6 g& J7 J  L% i5 k
    / f) \; T1 k0 a( Z$ g5 y& Q; `
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。* j$ l. I' j$ X- l4 ^+ K$ r
    Out[35]: ['a', 'a', 'a', 'a']: R9 [1 U3 H1 z' R8 c5 C6 G8 D

    6 i" z' w4 W. [9 I9 R* j  k! n# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。: c+ y8 Y) }9 L8 p+ F; i
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。) i+ i2 G6 U0 |1 ?" l% h2 H
    re.findall(r'\\', 'aa\a*a')
    # p/ u% L3 Q3 K7 J7 g[]
    6 Y! e. @- n* \( m8 ], v6 u$ m# h; f7 _9 B. V. T: O" ^+ D9 n0 g
    re.findall(r'a?.', 'abaacadaae')
    ' c1 l: X/ P9 `5 T" ]Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    " y% m' Q; P/ g2 s4 a/ s+ U8 I% H
    6 K% Q' E+ U) g+ Ure.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表) |  |5 ?3 w" D; P) x$ p0 U3 \
    [('width', '20'), ('height', '10')]
    # [$ S! Q2 _: M* ]6 H7 h5 b" k' b6 e9 S  }" @- `
    13 I; ]3 a2 ]! u9 ~7 P* Z
    2$ w5 _# j+ _' }5 ]% {# Y
    3# H6 y% }' M( K
    4
    + S4 u+ c$ k, I" H! f& c! `5
    ' H: u- }# p/ |6 S" I6 K' O6
    % X- U+ T% y% H7! ?$ [! B+ s5 a; x: ^
    8
    2 N& w! H" V4 o9( [7 [( a9 l- e+ h2 [, j! [$ b
    10" v. J# V$ M/ o, {( O$ }$ n$ W
    11  i" U: l  n( P/ z; i* Z. F' `
    12
    : q: G/ Z0 E9 H2 E% d( \- H13
    7 v7 W+ ?# v4 H" M14- d! y1 K* r' f- f  ]
    15# S, ]3 ~: b! `- A$ D, \
    16
    ; J; V; D/ J" ]% z178 V6 X4 a( x, E6 {4 R
    18
    0 o7 }  r, x0 y1 d19' J  H3 p4 P  |' j! `
    209 I" ^1 D: `0 u' _4 Z& U
    21
    # o9 X+ _2 F2 W: M, ]+ I" \% ?22
    & T  i4 }6 `2 _( {5 p23
    ) E- u. @* R9 Q% \- K( E& ~0 g241 e7 n6 G) X4 I3 v/ w6 n3 C
    25% H$ o* L. ?1 l8 a/ [/ _: f
    26
    " H9 X+ A1 \( H7 W5 m; |27+ c2 k* Z+ c, A, h
    282 p. P1 `0 D) f2 C) j/ x
    29
    % B9 s5 c0 y7 G" Y5 f30
    5 \% o8 b* S/ c% A; j2 G/ Y1 Z31
    5 M# u( v/ s* f/ q32
    ; p) x- C/ ]% Y% C33; @/ J) Y7 n! F* A7 a: U0 G/ ^
    34# W% x/ }$ ?8 f+ t( q! L
    35& W$ Q: }0 R$ o- E% z
    36/ _2 i3 \4 I  L% A" z1 V4 l. d
    379 `2 s5 B$ O- z
    8.2.3 简写字符集
    % X$ w; G  v5 Q+ Q1 x9 M( Z则表达式中还有一类简写字符集,其等价于一组字符的集合:
    0 {! [  t$ }+ T1 l5 p  D) F5 j5 Q" a! P5 z1 l$ l
    简写        描述
    1 Z, ?: L1 A+ B, y* v2 M' x\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    ; `7 K- G) O/ E: ?\W        匹配非字母和数字的字符: [^\w]
    , B: X: n' j* n$ X( v\d        匹配数字: [0-9]" b- u: ~1 f2 [  z) ]1 \
    \D        匹配非数字: [^\d]
    $ [* L% p* l, }8 j+ Y9 r5 [9 b5 l7 J% P\s        匹配空格符: [\t\n\f\r\p{Z}]# S& u. q6 U2 Z4 U& A; P& {
    \S        匹配非空格符: [^\s]
    ; o7 L4 J  k% T& ~\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。& O9 [% C; t5 d: S7 G
    re.findall(r'.s', 'Apple! This Is an Apple!')9 }% Y( ]7 A1 G* r
    Out[37]: ['is', 'Is']5 k; n. j1 z! _' I! S  @
    ' G" ^' w6 Q! r  J- r
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次# |3 P& R5 k! ]% B
    Out[38]: ['09', '7w', 'c_', '9q']
    # z# Z! m! o* p& K! D9 p4 J9 F4 y/ {8 K% y( j; |* d3 I; C8 }8 F5 ]+ [
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)' X5 Z8 i+ T- p2 x
    Out[39]: ['8?', 'p@']
    $ B+ B; M4 L) j: _. D' F0 T* j( N3 Z9 P! y9 @% Y* _) F. b; c( d
    re.findall(r'.\s.', 'Constant dropping wears the stone.')' \  _% A+ q3 P2 E: a8 K8 v
    Out[40]: ['t d', 'g w', 's t', 'e s']
    ; I: \. D; [) m" u8 g
    . J7 Z& M$ J& ?6 v% p4 gre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',3 L9 L, Z; J6 @1 r1 N5 d& r* G& U
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')! M& }2 V) n8 j4 V. ~5 O1 X, N

    6 m3 x% @8 o; P1 o' uOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    0 B. p' m2 b& _' X" H! @
    8 g; e9 W$ v. A6 h$ J6 |; U1
    1 w. U% w  {7 x: P' s! X26 l/ ?6 m3 r9 j# z' e! ]9 \
    35 f- h, w8 y  o% ^8 e; n
    4
    6 L9 c/ \/ n$ a! u7 m* J52 `5 F  c% J, T4 p6 U$ e9 G
    61 b1 y' S9 K9 M! k+ D. j( ^/ h
    7( a) Z6 K) q" n3 p# T
    8
    8 r  l7 ^! K( G" ?9) W$ Y- D- i9 H2 h" c, Q
    10- @* \) q0 X( p6 R, Q' P
    11$ G" L/ i* K6 T
    12- K: f6 ^& i9 [1 l6 r: s0 g* ^
    13% H5 p0 l2 r* Z  v3 i3 Y  f4 E
    14
    4 ]2 N0 S- L( |& j2 ^152 z8 n; f. I/ `
    16; D8 _* n3 u! I% t
    8.3 文本处理的五类操作
    1 |- O, |) J( \  N8.3.1 str.split 拆分
    - K2 w" o: v# {5 O7 Y( }) E7 p" l  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    0 D3 H' k/ S/ w1 o; j! M* J0 l7 }$ u
    s = pd.Series(['上海市黄浦区方浜中路249号',& W7 ]& F% R8 E7 T# h
                '上海市宝山区密山路5号'])
    # M+ Q/ l. d% X( p3 E5 D6 o# }# L  v1 s# \1 T

    / {/ U/ d6 u) Y4 A* Ls.str.split('[市区路]') # 每条结果为一行,相当于Series
    " K7 _$ Q8 ?* A9 @! h4 vOut[43]:
    , E. |' ?0 a) Q' s& S0 k0    [上海, 黄浦, 方浜中, 249号]* }& Z5 ?2 U+ y& s
    1       [上海, 宝山, 密山, 5号]& e) }9 f+ ^" _9 y0 x* g
    dtype: object
      I7 X. n# n4 \7 c, M5 t5 h( g; n2 g' i6 c! b$ ?  g8 [
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame3 \0 `1 ^# A/ c3 e4 O$ f
    Out[44]:
    3 ^4 y' c/ a( F9 @    0   1         2
    1 _0 y) \/ p- R0  上海  黄浦  方浜中路249号: g# H& \$ A! Q- A% N4 u; H$ a
    1  上海  宝山     密山路5号
      m( }4 A" E5 |% ^) Z1( K7 v4 R- Q1 k; r, n
    2
    ; o- l- W& i5 @7 I2 H& D& q0 ^" E3
    9 ?6 k; x4 _. \7 q+ {4
    ! l  z; j" Z5 j, v9 |0 D5
    ; y+ F" d0 {& ^+ t$ m6) \+ L# t- l- \4 H( A
    7. f* `3 e. N) P/ g' t
    87 o$ M" G5 f1 J0 f0 @$ @
    9
    0 `* ~) E( I! J; c  B109 B4 W+ m: X" I6 I/ y
    11. g! X3 z& [( N( j0 S: M
    12& ], h* Y4 J& |( f4 [3 r* T
    131 Z( y  ?1 O6 o5 I
    14
    + Z0 ^' l6 ~1 b; W- [15
    - d4 I8 `; S/ G& t4 H; ^  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
    ( }( r: r/ I* P5 ]* N3 D% X
    3 P' y1 a! z: X' p- Ws.str.rsplit('[市区路]', n=2, expand=True)
    # S1 Q' R3 ~) j/ V) ?7 \Out[45]:
    , h, _9 D$ ^0 E! S                04 `, d; C0 J5 I4 F. F
    0  上海市黄浦区方浜中路249号" W# N7 f7 G% p+ S3 q" }$ k
    1     上海市宝山区密山路5号
    # F) w5 m3 Y' |+ b, q" d1
    9 }+ n2 c* h3 w2 a- o6 Z$ c2: [) c# H! c7 a( U
    3
    4 y2 \5 d! h, A47 p2 I, K+ U0 {  u1 `; k0 z3 A! B
    5
    $ I5 j$ i6 N+ c7 U) b% @7 n+ V8.3.2 str.join 或 str.cat 合并% ^% Z3 @% |3 o
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    , k# z+ I; C# O6 zstr.cat 用于合并两个序列,主要参数为:( S# U. f6 S  v5 v
    sep:连接符、
    " V0 P# V& @) sjoin:连接形式默认为以索引为键的左连接
    * e. p* W; p  O: q. A7 s# a2 gna_rep:缺失值替代符号1 _! k& R( ?, c/ ~; ^6 G0 b
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    : w$ Y! Y/ V8 g/ X6 d5 Vs.str.join('-')
    * j) \* c9 A" U8 _, u; hOut[47]:
    ; u1 B5 t( p+ S* W; m0    a-b
    ( b6 T& y0 G* A$ I) T  `1    NaN' l, ~" p/ O( j: e1 G3 L! ~
    2    NaN: m8 n$ Z; ~8 W, {1 U
    dtype: object
    ! W" y. |  W# F1 d18 ~3 P, X7 X" B  c# Z$ s8 C
    2" |% {) Y" S9 R$ V% c' q
    3
    * h1 x# S+ O0 _5 @4) {" q: g6 a4 Q' x  ?
    5
    ! C6 m' C. z5 a* n" a1 q. Q5 K6
    ) @! ^; G4 D4 \5 @5 p79 q1 z% `( N' L' J. v
    s1 = pd.Series(['a','b'])
    4 o6 Y5 m( q$ w; C% Os2 = pd.Series(['cat','dog'])
    ! w4 F8 r( d8 N: d4 l$ Os1.str.cat(s2,sep='-')
    6 d% p' m( \: f0 K- c! kOut[50]:
    - A" u1 y/ {! d5 j0    a-cat
    9 {( x! O) t+ }1    b-dog
    , m6 I2 V. _" N" w  [dtype: object* G, A& p- j+ P
    5 j6 r4 Q/ b# ?6 a  k9 a! e
    s2.index = [1, 2]/ R1 I/ g( M) K: c: o( v
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')
    1 Y$ ~9 v$ K; S+ w6 P' S0 nOut[52]: 5 }' U. _" j5 M( i2 I3 M" `1 P
    0      a-?
    ' m% R& G) A* P1    b-cat
    0 A7 @5 T2 V9 ]+ ^) E; t2    ?-dog
    4 P  K/ }) O% a+ i% X% k" cdtype: object3 H$ g3 }9 d. S7 |
    1
    9 t! d& p; k: ]: i4 v, ~, J2( T9 c% z+ T) K
    3/ S) ?' V  ?/ c
    4# p8 P. a1 o1 A; o( P8 l: b
    5
    7 _4 h, i/ ]9 [+ x, Q' G" V6' E# x5 ?2 l. h1 d1 I$ W: }) ~
    7
    " t+ T8 s, m8 |5 J8
    3 d  y6 G: ]6 E) H& @" C90 r, Q# H  ^4 n, k
    10
    - q9 Q3 H5 g, E3 c5 r114 x9 N( G  \. p
    12
    7 B. O5 ?6 M3 X  Z/ _( R8 L0 C139 J0 n  B% |5 x3 m8 V
    14
    % Z9 j: l! O: @/ L4 _15
    9 K. B6 {3 t+ m. F# _8.3.3 匹配
    ! Y# v, y1 F) \% H- b6 l7 i8 `( sstr.contains返回了每个字符串是否包含正则模式的布尔序列:" I* s3 i" ]2 F3 N
    s = pd.Series(['my cat', 'he is fat', 'railway station'])
    * m. l/ U2 o2 l8 N8 Zs.str.contains('\s\wat')' s& K% O* H- C7 e* A4 T5 c/ A
    / ^; d4 Z. I3 E
    0     True
    : e' q+ T0 g& q7 q1     True+ Z' l3 I- w7 r1 ?6 Z
    2    False) c, T  a& `; \' j* p) W
    dtype: bool
    4 u2 {5 [1 B% x: e% M1 r1
    ' u, B0 |( b! |% T, \2- R# B" g# `. w& p
    3
    9 d% I; L9 J/ p1 \9 M0 O& v4
      S! z. E* I. {+ l! ?4 l4 s( I, C5
    % j6 I! j! h' L1 l3 J; j' j. ]67 ?  C# `: C5 b" j! Z% {
    7
    4 v  d  s6 c0 k  \4 h, Dstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    7 Q- b; g) e5 _+ G" I8 es.str.startswith('my')
    / s* Z; M4 p" n& I
    ; S! l  S4 ~: p' A1 i6 ^0     True
    ) s8 E. {5 O9 M1    False
    # f9 S/ a4 Y. b8 t2 u2    False9 h! q9 U% s/ v- n% H( N/ Q
    dtype: bool
      v% d$ M; o* W& q2 p" P$ _1
    9 S/ O, g& {8 m+ \6 o4 j/ o1 j9 Q2
    4 \9 w$ @& e$ U* D5 {4 {2 M+ N38 ~% c+ ?' Y. l
    4
    ! s7 ?% g2 O. P; V0 l53 ~; E5 `, O3 _& `/ T
    68 w7 J: s+ [9 q! W8 \
    s.str.endswith('t')
    0 X( x0 |# j, b: F. ]! }: Z& K" u* K: y9 O7 q4 h5 S: y
    0     True' m7 F& `& a! D- B9 b. o( {" e
    1     True
    + T, E& l5 A. n9 j4 r2    False9 X  b( I& S  m; `- S
    dtype: bool' Y; [& p0 r- O
    1
    : B; H3 h- j7 N$ `8 t/ T22 E+ N- `$ D( X. H% Y4 r
    3
    0 W) @9 \2 m. Z. f9 E46 M! _& \, V: V4 K2 R4 H7 d+ h
    5
    # D" H5 f# C  S6- J9 o+ K) J: r8 [: I
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)* c$ U2 t- u; l
    s.str.match('m|h')
    5 d, P; `- @# w. r( N; e8 o* S1 y8 Ns.str.contains('^[m|h]') # 二者等价
    0 b& F- r: O' C  P5 h2 Y8 _+ R8 Y2 [
    0     True
    0 P; {3 F+ y1 i* P1     True
    * j# S% b7 d5 V& |& m7 |8 Z" o8 g) H2    False
    - E# y  D8 O, j; ~8 ]8 x6 Rdtype: bool% k* X% I, z; u3 |
    1# R1 ~+ M7 g" R$ B
    2
    . Q' K. W0 ^1 N. \) ^% Q$ \+ E3
    : v# r2 f) P. E% q7 d, J6 p: x1 \4
    5 V! z, A( E3 F# x- X5+ K. p& Q% u. r4 N: M' [
    6# t- ?8 j7 [7 j- U# j$ F* v  R7 Z
    7
    4 p: p6 b+ M: es.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
    2 g% g! E( Q4 u, q, C. A* G# m1 os.str.contains('[f|g]at|n$')       # 二者等价
    8 V6 i. y% r. w/ f( Q
    ' ?" I( E7 a& j- m$ L* Y0    False
    / N1 q) P0 U9 C9 N6 o$ s1     True
    & e6 c$ W# U, B: r2     True
    ' x! c7 S& w& b: T) P, C0 _dtype: bool6 \) |$ U" r1 i+ P! `$ N3 U8 F, D
    1
    + k& s. I/ U4 K0 n2- Y' s, T) T( S7 c) N
    3
    : o: b/ [" |7 u9 o' [45 I; M1 g$ C" G( C: [) d
    5# y0 {& o. O/ x: W
    68 J3 N" o/ c/ U: J6 i* F6 v0 p- ?
    73 P* P* L- \6 x! _8 n6 ~/ d& t, U
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:% M# X, J% M2 H+ F
    s = pd.Series(['This is an apple. That is not an apple.'])  o% U% O/ Z( ^" m- V
    9 O/ p% j  B( O
    s.str.find('apple'), j" S( K& {  N+ H+ P& u: }2 M
    Out[62]: 3 B# A3 F7 b) x+ P
    0    11
    & d/ \# P& p* Ddtype: int64# O; V' l1 m/ J+ w0 G+ k) G
    " X  _/ _- J7 S- a) M& o3 ?
    s.str.rfind('apple')0 u! f8 H: j0 K9 ~+ c% s* W
    Out[63]:
    * ^$ ^4 S' J4 i9 t: W4 d0    33
    4 e) t3 J$ |1 t( V/ Wdtype: int64
    ( j$ F* S" m! @2 ^! L- y' Y1
    / v) r  N# H2 N2 o2
    3 x9 o% c$ ^( i. v& s! a# T39 I( k* A& I2 `+ @( n
    4
    " j5 V1 v' }, M: B- P5  F: m* K) ~, g8 h
    6% A1 f9 o1 M+ s( E3 ]
    7; `1 T3 I  h1 l9 {# N6 O7 [
    8& m3 ?4 x& y- o6 `/ W) w6 i
    9: m" }" d1 e1 A% T1 s
    10$ y( \. S' O8 Z
    11
    $ o4 P8 a: b/ U/ N: r% F替换
    / q# f+ z/ A5 D8 mstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    0 c* L4 V' O) Z2 ~4 J) fs = pd.Series(['a_1_b','c_?'])
    % z9 I! c9 P: K* l" L; ?. N& \# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?$ Z& o+ A! m7 K: }0 T4 B( n8 m- E! z
    s.str.replace('\d|\?', 'new', regex=True) $ M6 ]& I5 r7 ?
    # n; t) ]( E. Y( Z1 ?, w
    0    a_new_b
    $ j9 y5 _, u4 R4 V/ P1      c_new: G( k8 s* ~. e5 ^" f
    dtype: object# s# B# Q/ f+ R
    1. y; q: t5 [, n6 ?) c, z" i8 x
    2" D! r2 K( @4 e, ?; E) l  j
    3" p5 N; }# x; Q' ?3 l
    4% K( M, @- P) I2 W" L" \) S) d
    5/ j4 N" f6 q! O8 Z6 W1 L5 o% {
    66 Z5 b1 w+ c7 x1 @, N
    7; E, B% [# J8 m+ O" b: K6 U( {/ C
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    9 d' k0 l! o: O6 ?+ D! g; J# B  R0 U1 b0 u" K
    s = pd.Series(['上海市黄浦区方浜中路249号',
    0 ]) X; \1 P. g+ }9 [                '上海市宝山区密山路5号',: o4 m' r, {$ H& c0 U' a" p
                    '北京市昌平区北农路2号'])
    $ D" z. \4 J, J. a7 ^pat = '(\w+市)(\w+区)(\w+路)(\d+号)'- j5 Q- e# Q, g- r& M5 R+ X
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}/ ]1 d: r" s* M: Q' Q4 ~
    district = {'昌平区': 'CP District',
    ( [' E* [' c  `8 H- n, z            '黄浦区': 'HP District',
    - Z$ c" E% c+ S& l            '宝山区': 'BS District'}# b/ s( D  {" L" |8 z* U
    road = {'方浜中路': 'Mid Fangbin Road',: x0 [6 G1 q5 y4 y! c# n. r
            '密山路': 'Mishan Road',
    2 B- ]( Y' w( L& g# E0 a        '北农路': 'Beinong Road'}
    . P1 l  W# j! a1 zdef my_func(m):4 m6 c# v7 q( ?: x# H
        str_city = city[m.group(1)]
    : d$ q: S0 ^. p' X( n! S    str_district = district[m.group(2)]
    , h8 M- y% m/ N3 x9 q! W/ }    str_road = road[m.group(3)]
    / k4 K) Z4 B% k    str_no = 'No. ' + m.group(4)[:-1]6 U! D; b6 w/ f
        return ' '.join([str_city,0 B5 W9 Z4 Q- t/ @3 z
                         str_district,9 P7 w* `- f* n4 i; t0 X; x
                         str_road,
    ( D* t' T$ h4 ]                     str_no])
    , G* S' ]: `! d/ u: {/ ]6 |8 Ys.str.replace(pat, my_func, regex=True)
    " q( n) l1 j  h5 t5 H6 `! r# m7 K* ~; \8 F* u
    18 X; W/ {/ n$ y
    20 M' ]& M2 u5 }5 _: N1 e
    3
    ! p- d/ Q& M! r, H  h( {' ^$ ]4# w4 r8 ^7 Q% Q- s' c
    5: Y8 |% g' F* h! U) B; N
    6
    4 g3 G( K2 I. t' U6 L8 ]7
    6 M* j6 Q0 U5 M; {$ _# ]* Q81 L0 b8 M9 z" H9 M. h
    92 H+ A5 h6 D4 T9 z$ V) r) w5 v( u6 G" B
    107 r. w: e5 f, f' B4 y0 [
    119 }+ S7 t+ C% t  ~' B+ Q! O
    12
    8 T$ H* h  u' ]" W13/ z( x+ ~) K4 ~
    14, _" N$ i6 w3 m- [, B4 p
    15
    7 L6 Y* S) n  T* @+ }  V1 W16# h1 g  s4 [) {- a. D8 C/ d% q/ m! N6 M
    17' B% t' w" u$ a$ C
    18
    4 r* H' X: I9 D' F197 n* y0 L7 @/ C
    201 l' i, J1 i& `5 j' }4 H* A: |
    21
    - m8 \7 L: L3 m: }0    Shanghai HP District Mid Fangbin Road No. 249
    * e" u; S+ e8 Z% l) z5 O1           Shanghai BS District Mishan Road No. 5
    0 S3 ^$ y1 r1 K% @4 [% Z2           Beijing CP District Beinong Road No. 2
    0 a6 j. E& {4 i: ?dtype: object
    % p* a4 H  Y: J' s4 V7 l/ f1/ i/ \9 W+ B% ^. F% G
    2
    ! _! `- H) Q; P! z$ a3! K# t( t! \9 A" q( ~6 }6 _5 a
    4& u+ B6 U) Z3 z' _# S
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:7 n; i/ ?+ [. z/ x4 g4 W/ }' C

    ) w2 m% F! d3 s, s+ J! X1 m# 将各个子组进行命名/ S! a: z; N5 K* r; Z
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    * [8 l9 l3 w6 h' L# ^9 S! |" O3 B  K5 jdef my_func(m):, o4 R" N$ T% x
        str_city = city[m.group('市名')]
    7 ?* V* }/ }, {# P4 z; h    str_district = district[m.group('区名')]' [. s6 `/ E5 Q* m" b6 j6 L2 r
        str_road = road[m.group('路名')]+ k# s" l% Y! s* i  H! l6 v
        str_no = 'No. ' + m.group('编号')[:-1]- i% Q5 {$ |& ^$ G
        return ' '.join([str_city,! ^9 F9 I8 i+ C. `" [8 [2 X: a
                         str_district,' l6 J# a/ {1 v0 E8 A- r
                         str_road,+ c2 E1 R, ?- p) S! A$ G( f
                         str_no])
    ) c' ]/ J4 g, d) |' Ss.str.replace(pat, my_func, regex=True)
    ; P1 x. A( Z* M5 S! i: C" I9 c1
    ! f" v! a: {' |2 a: l9 [/ A2
    ) T8 i& g8 k+ ]& Z" p1 [3: V# U- l4 p& ^1 e& @* R$ _
    4
    & l% V- s, u* r$ D3 t* h( J5
    % z, ~, k% @1 l" ?/ l, h( ?7 `6" M4 O3 V, ?9 v
    7
    , B1 \5 @- a) C/ W6 q: v83 h5 L& C- @# S' w3 B  o
    9
    5 i6 N! u0 D9 J, w* a+ X10$ D3 L* b7 E0 y1 P
    11, U/ ^- x. j& Y! e* l- ~- S
    12
    7 k' R* x; w/ @2 e+ M5 x  \$ u0    Shanghai HP District Mid Fangbin Road No. 249+ @7 u& i# o1 L& c# e
    1           Shanghai BS District Mishan Road No. 5
    - T  N; v$ o3 U$ D2           Beijing CP District Beinong Road No. 2
    , s+ e  i2 A! \* Q' w6 @6 n5 f% m+ udtype: object# C" \: m: ^$ y. L2 i
    1
    % ^  l- z" @: B* n% K2( M, z# _9 n0 j& r7 H' Y
    3
    8 ~" ?, b* C! B, T! }% T+ u4
    . z0 T* i6 z8 v: [+ h8 g8 D  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。- |  D( o+ Q/ O
    0 ~! G9 }8 g) p; |* p
    8.3.5 提取
    ' @- |6 b9 E8 L! C5 U; A( Bstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:2 r* E& f8 B% ]: b+ M# t9 i
    s.str.split('[市区路]')/ P# K- Z* C1 @3 V4 s2 t
    Out[43]:
    # J2 p6 ~9 W! n+ S' K/ F0    [上海, 黄浦, 方浜中, 249号]
    . j0 k  Z, x/ K+ o1 s1       [上海, 宝山, 密山, 5号]
    ; e+ S0 @( J2 x" \8 Y# @dtype: object
    4 O" M$ F/ b8 e( b  {
    $ f3 v* s$ M( h* tpat = '(\w+市)(\w+区)(\w+路)(\d+号)', J) l' o( \3 P4 s  |
    s.str.extract(pat)
    2 J0 G7 H0 u% aOut[78]:: ~; M3 D  K1 y8 ?/ T* y1 \) b
        0    1     2     3; i$ y3 D" G5 O0 a
    0  上海市  黄浦区  方浜中路  249号
    ! J  M$ ?# u* P  @/ a9 f1  上海市  宝山区   密山路    5号
    ! ?* N9 _* a; J5 |) H2  北京市  昌平区   北农路    2号; k$ F9 s; V. b4 [5 m& V  P
    15 ?1 u7 S3 ~- H& v" N7 w  W# c
    28 P! o2 N/ E$ f+ v. L
    3
    % y% `# C! T  Y3 o" v) H# S47 D3 R- h, n4 Y' q$ D, \3 U, o. R
    5
    2 j/ m+ k& ~& v- Q) t8 Y0 B62 D2 Z7 y, u; U/ C+ m
    7
    # x/ h: v/ c' ?8  }6 T) s" ~& E7 R1 h
    9
    ! ]+ J7 T7 ~$ o3 L10
    3 D5 V0 _2 ?: c) h: v6 k( @11
    / k: {2 J4 L5 q- W. O12" P; L) `/ }4 ]& n8 ^8 x! f, }
    13! e2 Y- u, `. V9 S" T) B
    通过子组的命名,可以直接对新生成DataFrame的列命名:
    . W7 R: Z7 V9 G; s
    : P  m* B, C! k# J9 J9 v! Zpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    , l- X$ _( y' d7 Ts.str.extract(pat)& n( W' p; t7 u  u7 A
    Out[79]:
    ( c4 t0 p! N9 c2 E4 v    市名   区名    路名    编号8 I: L0 {: z, B. ]5 I' S+ l
    0  上海市  黄浦区  方浜中路  249号
    9 @- {5 T: e% A* F5 \5 N1  上海市  宝山区   密山路    5号0 B# @% u8 n$ P8 y3 T
    2  北京市  昌平区   北农路    2号
    # _! Y7 }# n' Z- Q* O" T! [1  {$ E- v$ F' `, _8 `: F
    2: {  ~& Z$ W% g3 z- H4 D5 S
    3
    * d$ g! K* E/ |, H3 m9 A' g/ N4
    % k# m' V  \% D: r5# F4 A: V4 h9 c' h1 I& I7 l* T9 V
    6
    7 J! `' S1 [7 L7
    0 |; l' O. S; j  [& d* estr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:6 s% {7 d6 x4 V8 Z3 a: B) Y
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])& X, o* j% @. k0 m
    pat = '[A|B](\d+)[T|S](\d+)'
    - {; C6 B0 i) X  u+ \+ c$ Ds.str.extractall(pat), J/ `# p6 T5 }% Z
    Out[83]:
    , s! c$ [/ g# a, l, s: e       0   1
    1 \5 R9 V1 F4 f' ]/ W8 ]     match         
    * V8 ]' _5 R5 Q) v$ ~1 pmy_A 0      135  15! ~; |" I' |4 d" R$ u& Y9 U
         1       26   5: G1 r# ^; t% T  @6 i6 W
    my_B 0      674   22 R$ o+ W' f# K5 O; P% C
         1       25   6
    * ~: u: p+ [2 A0 h3 T1 n1! s2 Y: d. e* ^  \0 @
    2+ ^5 a' @. P% r2 e
    3
    . ~+ L4 y7 E5 j8 B8 v; n2 K; y4
    ) H2 B0 k9 d" J4 Q5 |9 B52 C, E* r: m7 D# `9 l
    6! a& g6 m, Z9 ?: R; J$ K7 e
    7
    ! S$ E) K  N. a( T* p; h82 m8 u  p/ n- W, U( v4 r
    9- _0 H7 I* P! ~' T6 X: I
    102 u( O6 t1 E/ M6 o7 O
    pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    4 r+ c. D/ `" T. Is.str.extractall(pat_with_name)# }: C$ ~; j" p7 g# h  x9 w
    Out[84]: 4 U- L8 @: b6 U4 T
               name1 name2$ g! `. d; Y8 C6 Q' D3 x) D/ g. S; P! i
         match            ! s% o" ]. }6 G. V. f
    my_A 0       135    15. j9 a  c5 `0 q5 x) z  m/ e
         1        26     5: D* Z$ [& P3 g! o( D3 I
    my_B 0       674     2
    / j/ q- C' C8 A6 C6 d$ Q, v     1        25     6
    0 z! x/ p/ n/ i. T1% L  }: V8 }5 B& g
    2# n; l( u/ d6 T
    3+ U% P8 P' j8 m, m0 [! Z; E& w* [
    4
    : G/ J% Q- Q+ P) x) M; j) U5# Z4 d4 H- M8 v$ f
    6
    / W: d& |5 r6 e) i# x$ V7
    . a4 b" |  e3 d0 h8
    # ?# o3 |) g3 _; R$ A9
    4 d) k& @% }# _' P/ P' j. hstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    ) f  q2 A7 Z2 M3 L7 Us.str.findall(pat)
    7 c% x$ d6 Y  H  n; d/ I: g3 d16 L4 x; V1 G7 D# y% m3 I3 d
    my_A    [(135, 15), (26, 5)]
    0 i$ G/ X- t3 L- X( V9 X5 vmy_B     [(674, 2), (25, 6)]
    4 Q/ V7 W- i. G# Ndtype: object7 e* h) H; A0 c9 p. ]) O5 ]
    1- u' D' S" \3 k$ k
    21 e& K5 w0 H! q& K7 }" S% r
    3
    , j" q; s) f* y+ X. B, r8.4、常用字符串函数) y8 T+ k- J% J- y; h' R- v8 X
      除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    6 R3 L9 E# j" o4 z3 R4 F# q' Z5 w' }! `5 {" z' X( v1 ^" J
    8.4.1 字母型函数# M- {# q1 f' s/ M+ o* O
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    ( w9 k9 U2 \- F) s  n, ^$ H1 v5 B
    : q! X! E+ D" `( Y9 }s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    4 j: W6 W' V' t0 E
    + N- p, I* \/ z0 C/ ss.str.upper()
    8 U5 N8 w, M; \9 w4 \+ w( mOut[87]:
    " ]" r* L" p' U/ ?. ^4 E& W4 R% k; ?0                 LOWER: X2 x0 j) S/ n6 N9 S  L& h+ R
    1              CAPITALS
    % u* W/ {+ j& L+ e. z; F& L2    THIS IS A SENTENCE% u' A4 ]& y+ F% T
    3              SWAPCASE
    ) A1 M3 t6 v1 k$ @  sdtype: object& \" g% _7 B& H
    6 R- c) M5 ?' A  I4 m
    s.str.lower()! P: t3 q$ X* |: B& [9 O
    Out[88]: # }5 \3 z, @1 z: Q* ~
    0                 lower
    7 q! p1 q/ n1 U0 a" o4 @1              capitals: g4 F% q3 y7 y/ F
    2    this is a sentence: H2 C0 ?) t" \- b8 z9 S* |, X
    3              swapcase8 A0 l# A2 o! m* `. r
    dtype: object. j# Z$ h. O2 d+ b

    6 ?3 G5 l0 m; W2 g; m* As.str.title()  # 首字母大写
    4 D! G4 u1 m3 @0 {/ |3 i  `7 \Out[89]:
    % `1 F! m7 ?4 [" }$ L0                 Lower5 |; g; n- d) @) `7 B* `: `: p
    1              Capitals
      `% l. X, I( V2    This Is A Sentence5 f; G% b' ]1 Y" r& A- k/ D
    3              Swapcase5 b  s1 G& C1 E' F& E3 `
    dtype: object
    ' ?6 M$ @7 u9 [
    . ^) n; j3 A: I- Ys.str.capitalize()  # 句首大写
    1 g: @0 `' a' y% J- v# XOut[90]:
    * L" k& H2 G8 c- D/ r, S, a! O0                 Lower
    9 g1 L1 B2 S1 `# ]1              Capitals
    0 d7 K- h( E# P2    This is a sentence
    - @, y; S, f8 [2 W8 S% d3              Swapcase/ a, c7 q0 O/ B2 V7 s2 O  a$ }
    dtype: object
    ' f/ I1 _7 x5 l, v' i) ^3 t) c1 E
    5 h/ e1 E0 c4 G4 _9 C. y# gs.str.swapcase() # 将大写转换为小写,将小写转换为大写。% ^8 P5 X0 A7 ^9 c' @+ B
    Out[91]:
    ' s5 _4 k7 P0 d- E  p0                 LOWER  }- e: c9 T1 g+ E
    1              capitals* A. ]  O7 S( l
    2    THIS IS A SENTENCE" ~; A: e9 b# D# n) g, c
    3              sWaPcAsE) {( P5 I, a+ z) U! g0 A
    dtype: object! W9 x, C! R0 J8 U& i% R) P

    9 e; A. N: L, N9 E$ a  Q# E2 Ms.str.casefold()  # 去除字符串中所有大小写区别
    6 l* C+ A; t2 R% @0 Z( p) |- ?3 D9 H( I4 G
    0                 lower
    2 v, G0 k! [- Y2 u/ }) R1              capitals
    2 v" ]9 \5 {$ X" V; G" m9 p2    this is a sentence
    9 s- s( J& r8 B3 I5 h, ]4 u7 l3              swapcase
    9 T4 a& N9 K# `3 V1 K. R3 i  [8 Z
      K& v: W( L" T3 F: W1' Q- I5 y5 j; b4 c0 J5 M* O  z
    2
    " T8 m* `: I. ]( C! W' M3/ z: Q/ K4 Q4 |. r) I
    4
    ) X; N9 Q4 q, {5
    ' J# s7 \7 t7 g$ C- E0 U9 Z, O; `6
    7 ]* t+ M$ {2 Q0 m- s7
    ; ~* O  f3 v3 d. i) K$ z# e8) i% W& }# y6 y+ v" y+ x
    97 R) j9 Q/ ]6 }5 m5 h
    10
    - J5 g5 H" L; d8 x4 o# F117 f8 M, p( w3 b' R3 `' M
    12/ \& h4 ^: G& s* F4 B+ c9 t, t/ }
    139 \$ H8 Y1 U, W) K/ `
    14
    6 O) U- z6 ?! `" H15( q% c: K% n/ A& `4 M
    16
    7 L$ [- O& B5 y17
    # Y4 Z- K2 t8 A; I) L2 d18; L3 V+ P  ~$ l" U2 @* i% z( u) r
    19
    ) \3 Z6 H1 d1 F0 Y( s/ F& q20
    8 e  y3 \0 X5 Z  S21
    8 D! x' y6 K. ]" ^7 j3 R221 z6 z# j# p6 B# y1 `, Y* ?
    23
    - T0 N; j; E  o3 [24
    - Y: {, v8 {  C8 c$ P& j: M25
    2 _( w% M7 s( x' y$ z& ?26
    0 z: {" m  l' U7 H- B5 G279 H/ P* u6 O- I& g# C
    28# T& T8 G6 Z4 g# `9 z8 o
    29
    4 n. ^1 M, z' S& X$ A: I1 M0 }30# H, s4 M: S+ \
    31- u, g. R" f; |3 M" ?
    32
    5 A" k' e( f; Y: Y7 u; W( c: a" [/ E33
    + l6 O2 ?* s# f# J+ k* A34
    5 m1 o5 O* P6 p0 P& p35+ A8 U. W% [9 e# d6 Q
    36" t! p6 O, L; e/ w- N
    37
    . ?* g# W! o1 E5 j& l38
    8 N; H' F" o0 T2 n9 Z5 P8 r39
    " L8 _4 x0 U  u409 L- J9 k) j6 m" \! S. c$ C/ U
    415 j1 m8 d; A. K, N% }* D+ M* i+ A3 v
    42
    - a& J) e# N: n43
    ' B. H0 V; w3 u+ U" p+ s  f5 S44
    2 c% u' _. M+ g( ]/ E! l0 O45
    + C# j7 b5 v/ Z+ R9 X3 j* I- t: k46
    # Q$ r1 e7 {2 n& P47
    - u8 Z' G0 U3 V: f0 S486 x3 |( F0 Y) R$ w% g- k3 g' @$ i+ C
    8.4.2 数值型函数  l/ L) F  Q8 t6 p
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:5 F  s5 S; g; N4 r9 h0 d
    8 p' \  y, f* A) H) Q& k4 X1 W
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:2 m) g( y  m' m8 s8 J: z; X- D
    raise:直接报错,默认选项
      X, h: A* N. C- B- e0 Q! r4 Mcoerce:设为缺失值: }, C; H  ^. N$ ^2 q# i: D# [
    ignore:保持原来的字符串。
    4 M! c! ]( p2 P! l. d! xdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    ! i7 E" M" R- `5 ~7 Ws = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])* H1 {8 B6 h% d/ ]. h
    # ?9 v! L3 A* t4 J* r, \8 ?" P/ C
    pd.to_numeric(s, errors='ignore')
    6 s3 x( e$ q' F: C" C# K- NOut[93]: 6 D2 C' |! G  D, t
    0       1
    6 K# K5 q* C& v/ `. G1     2.2' }. w8 b3 [4 q1 {
    2      2e
    1 m* w6 ]  [% t; I3      ??/ K0 ~6 o$ ~. @
    4    -2.1* `5 c5 u9 t, y; V# f
    5       0! D. W. [1 S0 B1 a4 v/ z$ E# F
    dtype: object0 K. h  u7 I" ?' n

    1 t* T. a  L9 y4 }& H2 ?3 p0 o: jpd.to_numeric(s, errors='coerce')/ j$ r% @( S6 ~  e
    Out[94]:   j* }- Z+ B# v# B) s
    0    1.0; d2 @4 P. m$ t7 i3 y$ W) Y* r
    1    2.26 t2 ]/ B, e$ W% m$ c! X4 T! g
    2    NaN! g4 z' u" H. J+ ?& W& ]7 {
    3    NaN
    2 V. l+ e3 n  d8 P( z2 }4   -2.14 \6 p  c: {3 V7 N$ I; `
    5    0.09 K# f6 z0 s# r7 w6 X  K/ Q  m" n
    dtype: float64+ L: j( F9 O3 V8 `! f2 _" j' U: P

    # ~. F7 r7 I4 @) h2 g( [. t1
    8 R) Q* ^* @; e) S) p26 X, i+ H8 ?& n
    3
    : E1 }( h% m9 U6 K' c( b4$ {# C# f! k/ x. e* u/ i
    5' n6 g: E* S" P0 m; |
    6! \2 H, K6 i  W! d" e9 `* L
    7
    6 w% W3 g; J  A. y8
    7 V! C8 G2 s3 o% }9* D5 D/ M8 z0 ^  q* w
    10
    4 L3 d- D/ q3 }: k: c11. ]& Z( }" Z) A0 }
    12
    9 [, U+ R! W% V; X& f13$ z+ i* X% E* [# N
    144 V+ a; {' a8 C; M5 v4 i9 g
    151 ]. A' ]/ \! }7 N8 m8 x. U
    16
    - }3 _* w# u( J* L& C17+ _. q; ?: k* ]2 B; {7 C& ?4 E" }
    18
    3 F, h3 R( p! \3 b2 A19$ }9 p- P3 ]# M: B4 u( ?3 s9 r& g: B6 W
    20
    # T- o* x. c- s+ j5 Z4 H21) X( B  L% P5 P; L6 F
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
    8 m4 S: u# [" x( Y& f" W+ A/ s6 T; i; V. d0 K7 G' F" i0 u! \
    s[pd.to_numeric(s, errors='coerce').isna()]
    . q' X/ P/ [& |: @0 l) ZOut[95]: ' F! z! L3 ^0 k- ~& [4 ~
    2    2e- w; A& l2 Q6 ^$ l% z
    3    ??1 u( n6 k( C0 @6 O4 l
    dtype: object9 G, ^/ p5 Z$ Q; M2 M8 H, r; N
    1+ Y8 v$ l. Z6 K6 [9 V
    2
    " G# i$ M- r, T+ m3
    & ~3 c, m# J; A8 u' O+ M/ }/ ~4
    * g9 G/ ^8 y6 [5
    9 E; f. `; J, K2 h8.4.3 统计型函数
    8 C9 z- k8 e; \* Y( p( K! h  count和len的作用分别是返回出现正则模式的次数和字符串的长度:+ e: {% h' D, r+ D- {5 x3 H

    ( [3 _$ `7 }6 e+ |s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
    ! k' q2 y. t6 Q. m$ @2 M8 s7 G( N
    % ]: N4 V% F7 z9 ks.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
    : ~$ Z* Q' h7 {8 O/ p& aOut[97]:
    ( u5 J* [( a' A, p& g" c0    2
    $ V! R- J9 g5 O9 p1    2
    0 G) [' `" t+ B& f3 Cdtype: int64
    % B1 v1 F1 Y6 S: V5 q6 `0 f3 P" s4 ?* s, x5 o8 X  U& l- f
    s.str.len()
    / k/ b/ X6 L: ?% J- [8 t6 sOut[98]: * ?# Q& R# ?/ q8 U
    0    14
    # J) f9 H. k% E7 C& x1    19( q+ J, ?% R' [; M% S9 t; o* ~
    dtype: int64
      f, N% W" D( i1
    $ U, X! r3 l+ B' Y; [2
    ! \9 A8 G$ y9 u+ w3) N3 u" n/ w, G4 O+ w
    4' E# |& V1 K4 o$ F. {8 m4 H
    57 v0 v& @$ t" L3 Q8 `
    6
    & P( {+ j; {, {; v" d! K. J& T7! W6 W$ w- b( y1 _# P1 |
    8
    + |' c- g4 R' ~2 Z" `9
    1 ?/ D$ Z2 @- v10
    4 P' I% O# [, F0 c% }* l0 Q4 P11- j, H0 ~+ R$ F, u
    12
      b' [- h2 Y8 {1 C" |13  w. x' ]" B5 `* E. G
    8.4.4 格式型函数( a+ u# V5 X0 u1 V
      格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
    / v0 G  C7 f5 L1 Q9 k
    . t" P; a$ a& o% s/ b7 ~/ j1 Smy_index = pd.Index([' col1', 'col2 ', ' col3 '])7 i* t! F- d, p* P2 L9 T! f) n5 W

    " z/ |5 F7 `6 I. z' f& A! xmy_index.str.strip().str.len()
    & {3 }; F) H$ P$ ^Out[100]: Int64Index([4, 4, 4], dtype='int64'). E7 i" a8 _5 D! X

    4 b0 R, G2 |9 O3 p: s* f. {my_index.str.rstrip().str.len()
    % q+ n/ k! d5 xOut[101]: Int64Index([5, 4, 5], dtype='int64')7 D& t: {, N% L' x9 f- J5 w; ~4 C

    / K0 J% k" \, y0 P5 ~6 B' B% |my_index.str.lstrip().str.len()* W, H/ k2 l9 E) ~6 p5 q
    Out[102]: Int64Index([4, 5, 5], dtype='int64')
    9 K6 B5 A  e3 x1
    . z8 t- h- b6 g1 y2/ t) Y" d( H* y9 F& s* |
    3
    7 \. Y. M: o0 h, e* y4
    % Z; l6 t6 E8 K5  v) b$ P+ M' l4 H
    6
    % {$ h# M8 r$ g8 D/ K- _3 D# y7  b; k0 v& @, ?. c- S# ^9 h
    8- n$ Z  G6 c' d( K# g1 g
    9
    * ^3 S5 O( Q/ z2 M4 d, o0 `% }. g10
    7 N2 x+ G* Y6 s' r/ c  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:+ w/ P/ I+ E  k' \* H5 x

    3 f: I# ]$ R( M+ C- Zs = pd.Series(['a','b','c'])
    + i1 h  F& o4 G( [! `1 D% b, b5 e
    s.str.pad(5,'left','*')
    " k) M6 d9 o6 W0 M4 r4 FOut[104]:
    ' z7 `6 q5 y/ H& z  W2 M) t0    ****a
    1 X9 i# r' z8 }* e1    ****b7 }: c& ?5 P2 B3 s2 v, c
    2    ****c) n" i, x9 }% h% Z+ y3 `! o
    dtype: object" Y) _3 o3 Y: }2 C# Q( X- d3 U7 I
    , |8 e& D* A) s" F* r( \3 u
    s.str.pad(5,'right','*')* X% J( q6 w- [1 u  ~
    Out[105]:
    : V7 H1 E5 b% y: ?: m0    a****
    ) @5 g, D% g. o6 H2 M. P9 R7 j1    b****
    $ F! R9 L& }* |7 v+ z" t2    c****, Y3 M* d6 }) Y$ j9 E0 u3 ~6 g, c
    dtype: object
    & M7 p( d8 @; J- O9 l" q
    8 V9 `' i- _0 e2 g( Z& t$ {% X. P2 ms.str.pad(5,'both','*')
    ( j) b" \( Y7 V# k1 jOut[106]:
    ) p) S! }: d9 [% T8 Y8 h0    **a**' Z8 d3 G/ f+ Q- q' H
    1    **b**- A. X+ U  C# W3 y* A+ ~$ X
    2    **c**
    ' C. H, j5 a* j+ i# f/ ldtype: object1 _. \& F) r$ m& v2 j4 v
    ( d! |4 D3 y# \" o9 C7 n4 `! ^
    1
    $ S: N, Z! C" N3 C2% f: o% `4 Z' o+ c
    3
    " b8 B) Y) Q& S8 |: d1 F4
    # `' L9 u/ g+ e8 q5
    / O! V3 N4 Z% I8 I62 T5 f- `! X2 L, y9 r+ Y
    7
    # q! O: a4 u0 r9 H8
    / k# j- q- _3 o! R  O/ e3 z* q9% P5 ]1 B* R" `' [, A5 t& e+ h
    10
    ! L. ]7 h  P: B2 r% [4 [11; ]! ^) e4 K+ e- T4 @6 [
    128 ?+ @" M7 P) ~5 e, E5 g
    13
    . l, o$ G' ~4 t14
    7 I+ R/ l7 P. |15$ J+ R; ]3 s+ y2 N
    16/ Q$ a7 _# H: U" m
    17/ x& a& G( M# z. l- y! i( d
    18
    # I9 L3 P& a2 E+ o- d+ i2 F6 u19
    & f2 n9 U, @7 |$ K4 M4 s20
    4 o. P; K( p# I7 O21
    3 h: H7 A$ ^3 F4 ]( Z6 s- P9 q) v22
    0 y( p. V0 m  j) \% z8 ~% i. M  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    3 N0 ^8 n6 f# X6 H% U- e0 r! g4 x2 H% v8 A/ G, `0 U8 e& `
    s.str.rjust(5, '*')* L) y% d' m7 _) \( Z6 ^
    Out[107]: ; u! k0 O# Q- l
    0    ****a
    3 o9 u  {, k% G7 V+ C1    ****b4 o& Y' {  x4 N; s, r( x3 X5 _
    2    ****c
    0 r+ S1 Z: ]. ^# ~2 ^9 cdtype: object
    ) T' {1 h4 k& d8 {* ]! Q5 C. c! U- E+ |) B& O+ G% p
    s.str.ljust(5, '*')" L  Z2 D  T4 T3 ~. v, [8 i- Z
    Out[108]: 3 h: w' j" n3 Z7 i+ ^% f. V
    0    a****- Q$ W0 U, ^8 p* k: u1 ~& c
    1    b****
    2 @# v9 U0 ]" L! k3 Q2    c****8 @( G  C$ F, F" i! I
    dtype: object
    1 X. J% D4 w6 O
    ( P  _" C4 J7 G! J4 |) Rs.str.center(5, '*')$ n. i" X0 Y6 M, F; w3 Y
    Out[109]: 3 {& Y+ E& M) a# c+ J
    0    **a**+ D! U* [' v) c1 B  v# n
    1    **b**# V9 t/ O5 u  Y: A8 L
    2    **c**! H- K; E4 i; g& r( g- L
    dtype: object- x& X. f( Z3 N  s3 Q

    / B1 K+ e1 Y2 n15 @' h% V9 R' t
    2& r* e6 w+ a! k9 k; H
    3, s0 t8 g1 e: p3 d
    4
    1 h  r! ]. v, `: }54 q3 {+ u) |" q- h8 k3 y) B7 N
    64 X' }4 ]7 h  [, Q+ _- ?
    7
    9 d8 ^' H& x8 e& `: @5 b8
    ' f2 [; g+ I, m6 Y2 M9
    5 a, c  n/ i$ X+ \10( ^4 s4 f' \8 r# _4 ^
    11/ G: J" C5 C3 D- p
    12/ P3 @/ T9 D) H$ `
    13
    # z, m# E1 ~9 C, {4 `% ?) a14) b/ T  n- A) k' Y4 X+ e
    156 G7 h- ]/ @; |- @) i4 J
    163 Q( J8 _$ j4 O1 e* Z
    173 |1 |, N& k) `8 A  K+ W
    18
    & Z- Y2 @6 m7 b19- p5 Q. s) J( t/ `/ ]8 M
    20( O1 c$ }% `9 [
      在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    " e0 y" X( g( ]4 ?( D5 I7 r& O+ ?$ j5 U" x) ~
    s = pd.Series([7, 155, 303000]).astype('string')# e* l$ u7 y/ q! [8 f( B
    8 B# Q9 _  A1 O
    s.str.pad(6,'left','0')
    / U3 J( {) d  Y+ c" FOut[111]:
    8 n3 J5 [1 C& z& W! y0    000007) g' H4 _- n  o4 p9 W: Z9 X3 Q
    1    0001557 ^6 ]9 F8 D% f& w  _- Z
    2    303000
    2 M% {: Q! Q3 ?2 X5 [$ `5 [dtype: string
    4 c" x8 z3 A! F# @
    ( E# A; ^" C# H8 U$ f* }: ~+ J: Ns.str.rjust(6,'0')
    ) j& ]2 }; _- e# l% w/ L  \Out[112]:
    ) n( g! m' e1 h% T0    000007
    . S, H$ G( B) m$ ~% k1    000155
    ) y3 W0 m3 s+ z) n4 E: ?6 m! e2    303000) N& d2 D- ~; C- `/ ~' d, p1 _
    dtype: string
    . E, D0 k% B, ~; N; \- \0 c( M  Y. N$ x3 q. U
    s.str.zfill(6)- q- A* H9 @1 J3 s; J
    Out[113]:
    % f* g4 @+ ^) a6 ~, q5 I. M0    000007" x) o! y& Y: A- T9 j$ s' O2 f9 ^
    1    0001557 T  f) x  f9 I+ E; j, j
    2    3030003 s9 m+ G8 s4 [
    dtype: string* d' _, D! S' O: ^* J* s

    $ C8 X  q  Q8 @1 C& j1' `* ~5 K( J* B$ i% H. d9 E
    2
    ; @) M; N0 u. N' v. M3 r35 V0 F+ O3 u3 N) [3 Y/ S8 K
    46 ^- h/ p: L2 B) D
    58 i8 x# Z$ Z, P1 X+ y# [
    6
    0 l- |6 p" k. H0 s! L! a7
    # P& ?4 V  w. b+ x89 ?, {/ V' m+ ~
    9
      [; t9 r7 s" W" y+ T10
    + H' p& x$ R  Q11
    2 R* U% r' {9 O12
    ) C( U: C* v2 `; ^5 t1 M137 Y! f: o, P7 q  W8 l0 P5 S
    14
    + |$ q4 G' m8 l  Q) y3 @15' i6 w* E1 s, l4 H
    16, [3 [: c) z1 o. `! J( [- a5 x  H
    17: P2 w* \7 A. P- s2 i% }
    18! _( h  O! d6 M3 j. y2 G/ X
    19
    5 Y1 K& w7 e" x* g20
    ! g$ _4 \* r& l; G9 x: \8 m21
    : N' x8 W5 F2 D) Q9 G22
    / E& h$ a9 S5 a6 I8.5 练习/ X; T3 r) E3 z% o
    Ex1:房屋信息数据集
    3 O% @: b, c3 P1 J" T2 D现有一份房屋信息数据集如下:
    5 a6 i2 ~6 i# x* a9 b- ?( b4 b2 D. i* V  i7 r, r
    df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price']), x9 E. G1 r- N9 E
    df.head(3)" a& s4 u' H% T3 e6 U
    Out[115]:
    , G/ \" _, t! `3 ?5 y* [) D      floor    year    area price
    % D+ x% k' r! u- C" Q0 J0   高层(共6层)  1986年建  58.23㎡  155万
      U1 a+ z& E, N1  中层(共20层)  2020年建     88㎡  155万* w+ [( f3 S% Q5 H7 x4 c
    2  低层(共28层)  2010年建  89.33㎡  365万
    " ?& X& `" u+ \# _. E13 _7 X9 @+ u. J2 k% [" U3 X3 m& x
    2: V7 s) B5 D/ u# u: S- R
    3" t9 Y, T7 ?) ~  }
    4* w- O1 `$ x% m3 r3 k, p; x
    5* e! d  E  g6 Q* U% F
    6
      p5 L" r; o1 k' \6 c7
    ) @! E' B: j2 u0 x6 e$ c8 c: o将year列改为整数年份存储。
    5 N6 h4 t# t% s0 b$ s将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。) S' D7 j& ~4 b+ ~% Y* I2 T9 M6 J2 r
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    - W& I$ q* U8 K$ N1 T将year列改为整数年份存储。
    / |" K4 c1 ^5 B# P"""
    ; S# m) G5 g5 b整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。! O9 A; [' V& A( m% V: |- |1 m+ Z& f
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    ' ~1 s' X. R) v% J转成int后,序列还有缺失值所以,还是变成了object。6 t+ \2 P/ v8 Q* O& R: C
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。$ e* j' i* k; b3 y4 W4 g4 _
    """
      y9 r, e* h" p+ |& t: X6 o1 u5 Adf = df.convert_dtypes()
    5 s$ K! c3 c+ ~9 F+ W* `8 E. Ydf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    ! u; n1 x! j7 _' |. ?4 Y( a9 ndf.loc[df.year.notna()]['year'].head()
    6 E4 _, f" {, t; Q
    9 s6 b) |& M! u7 d& w' U% G0        1986
    - j3 R( l& h9 Q: b1        2020
    ! w! v+ |# Y. e6 B* v; ~) ^2        2010
    1 D! V  T% H$ q2 Y& |* y, l3        2014
    ' g$ E, t  P( Y; i4 n* N, M4 y4        2015
    . w% [- V- w+ q0 S, x& `' sName: year, Length: 12850, dtype: Int641 f: A* T) r" C# i0 s9 ?+ a
    : ?' h/ h% B" {% X7 S& `5 o- I% [
    1! K3 F: i, a+ z. y! m
    2
    4 z! O  o: N0 N, ^! D3
    , A( t, V6 ]2 Q, f4
    0 R& o; r1 y9 m: _! j- K) ]4 t9 C0 i5
      r: z( f( @2 ]" m' w/ z6
    # D2 V$ W- z% x2 T6 G( [7- {9 i& B) n9 l) f
    8! @# ^3 w! x; g: N5 S1 u
    9/ u5 F1 y3 T2 k/ q/ K( e
    106 c9 M3 K! l0 y
    11
    . V2 q, O7 p3 q* W$ K12
    ) Z* H/ j' B8 D% G7 w! U3 H13
    / Q6 Q  v6 J4 `5 o  Y$ d) m1 [14
    7 `; G) n) K1 r, K4 y4 l9 p15
    : m' i5 s7 I" R0 z16" B" O1 U' t  c( B
    参考答案:
    , A4 U8 C% Y2 y: b* X7 C8 H/ g5 J9 i, O7 ~: @9 {
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
    # u, i( J. V+ Q* u8 l
    ( }# p7 q; L5 e" z& z) U( vdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') / W: u/ g8 Y( b8 I# t$ O
    df.loc[df.year.notna()]['year']
    0 c8 C5 A( E# F1 O- B# R8 B2 ~1
    8 ]( n4 x8 v, G: t. W3 @2. ?+ c/ P' f# D1 S
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。) Y5 y9 ~2 f; i6 I$ L
    pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'7 K: t4 R/ a7 S- h3 @* O$ E% H- u: \
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次  A4 {% `, ?/ S. j2 T- H+ u
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型7 M& o' {" Z! v' u& [
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              1 y/ U; Y9 ^2 X2 v5 ?% w" L6 g7 N
    df=df[['Level','Highest','year','area','price']]
    ( @, q, ]2 W1 u) ~6 A) A; x3 ~df.head()
    2 K' k- {$ t. y3 h
    2 y1 P6 w7 C# B- \   Level  Highest        year        area        price- M7 B9 Q( v0 X. a- H/ u
    0        高层                6                1986        58.23㎡        155万
    1 T7 H8 V4 Z$ }) |. |+ \1        中层                20                2020        88㎡        155万
    4 }/ Y% d" t$ j( b! a; ^* D1 m2        低层                28                2010        89.33㎡        365万9 ^" h( {8 C. m8 }& U( u! Y; K1 V
    3        低层                20                2014        82㎡        308万  q- u. U! d8 R: S: R
    4        高层                1                2015        98㎡        117万* k% d: U" [$ M- J
    14 f3 @! e- u" v
    2# ?: G$ x7 L% P# _; y0 x
    3
    * @: D: H6 k0 x40 u* Q5 S- b% k8 S0 S
    58 ?3 H% c" p0 X. v( M, M, i( |
    6
    " d; X. J9 Q! w/ Y; m) U7
    * o9 e# X0 B" i) l9 v8
    # M) r: J( \  p7 \7 D) g; K9
    - ]' W$ V( @+ A  n9 D4 h) S10
    ; l( H1 B: n. }* P114 x) V; H. S/ B% c
    12
    5 D: q9 y8 U7 q13! W3 g- j) P' x* `6 a
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了; R  n9 i  U; }, r# E3 h
    pat = '(\w层)(共(\d+)层)'  D: P9 `# q8 r) t# c' S
    new_cols = df.floor.str.extract(pat).rename(
    " Q) o  m1 ?6 v: t) O                    columns={0:'Level', 1:'Highest'})
    $ c2 c. {" L! w$ u
    ! _; K& {3 o  o' Y& Qdf = pd.concat([df.drop(columns=['floor']), new_cols], 1)
    8 \* Z4 U& r" K/ W1 f2 K0 ^df.head(3)# _) G/ u7 I. X$ S5 s5 P' g" H

    7 H4 p1 }1 a. `Out[163]: ; J% t6 u& ?8 q9 m
       year    area price    Level Highest
    8 Y' G$ M$ p/ y1 ^; [7 s5 C0  1986  58.23㎡  155万    高层       6
    0 n! d/ q" Y9 A! |; _1  2020     88㎡  155万    中层      20# S4 _* ?- H4 h* t, F9 w
    2  2010  89.33㎡  365万    低层      28& i4 X0 O1 B/ o! j& |! r
    1
    4 i: o9 o! k5 g# Q2
    ( u6 A) F. z' k4 o- S! e3
    * p$ x4 A0 z, Y4
    - W! {' l, |& p# {. V5
    ; V, S* V) L' A+ X2 w. R- a, ]6. q* y/ A2 u6 s$ U9 ?% k% E8 d0 E
    78 n0 k) M2 {2 O  A1 p
    8- t* m, L1 j) }0 [# u- F& I
    9
    0 _* e) X* [( e10& o% J5 \" X3 k- D
    11, r- I4 e* L" \* K3 G
    12
    3 J; H5 z5 b3 r* l) Y& N13# [& A7 X, T4 A, q
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。, ?! h  L: V# G8 r. F, @% ^* l
    """
      P/ l% u: E/ r: _0 Astr.findall返回的结果都是列表,只能用apply取值去掉列表形式
    : a: u8 U) c/ s% K' [参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    ) P5 P+ M1 ^3 I; r, j由于area和price都没有缺失值,所以可以直接转类型; L0 i; p1 N, ]4 P" n6 ~% h. v
    """7 [' q, ^! _  K. b9 e
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0])); y" p0 p% m  `9 v. l
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')3 y! b' a5 z+ v0 ?* H
    df.eval('avg_price=10000*new_price/new_area',inplace=True)& D$ O- |) U) o3 e1 o
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    : N1 J- m9 x: W$ a& [# 最后数字+元/平米写法更简单, }- x# I# l! W; q, O, u
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'$ V1 m  H( W9 u: R6 X8 m
    del df['new_area'],df['new_price']
    ; z5 P( s% r6 edf.head()
    ' o7 g, ?; K, @5 h, D* _3 N' t
       Level        Highest        year        area        price        avg_price; w4 v. w: U! w
    0        高层                        6        1986        58.23㎡        155万        26618元/平米7 e# e- Q" O5 J: m8 ]/ u+ C0 O
    1        中层                        20        2020        88㎡        155万        17613元/平米
    ( @7 [3 ^4 s9 E1 q3 |7 l2        低层                        28        2010        89.33㎡        365万        40859元/平米
    8 a7 W+ t; O; Y5 |! c2 W" g3        低层                        20        2014        82㎡        308万        37560元/平米
    2 p* o) x) N1 M5 I% Q6 t4        高层                        1        2015        98㎡        117万        11938元/平米: {* @% |4 s3 p8 N- p

    * ~! t+ B; }8 P2 t. p11 S; S- p3 n" e3 P6 e8 t( }
    2+ z8 ^' E6 w7 b; R2 y9 a- `  S
    3
    ; t* _  S8 U& Y% Y" \0 k; L* k! s; _47 k0 U7 I8 T0 r; x; p/ b5 U
    50 _! o/ q- Q; y9 W
    62 \( `" R! H0 N9 N+ p% G: f
    7# O* H: I7 Q3 X. I3 w9 q0 e
    8' a! `: m5 p% |7 E7 n1 v
    9# u- q* t, y- t9 I+ p! x
    10+ y# N9 D' l3 N- C
    11
    # o/ n& s8 u+ O7 g1 ~12
    0 z' v4 {9 \4 v2 t130 h' G  h6 j) g5 g& ~; Z
    14
    3 Y5 `" n1 z4 }15
    + @- ~$ o% \6 U: G16
    1 g$ i1 X" u1 t" W; {4 s$ `3 @6 h5 ]( e* W171 i4 U7 ~( s. M1 v
    18; I$ ]4 i8 a5 c
    192 e* r& h- n! c4 y2 {
    20" b, u  p3 w8 P$ w/ D9 O) ]+ o
    # 参考答案
    - z7 ?/ Y' X# {9 x+ ps_area = pd.to_numeric(df.area.str[:-1])
    1 h# g/ t  n: k8 g9 C$ S6 {s_price = pd.to_numeric(df.price.str[:-1])% E9 I( N7 o+ w' f5 _4 y
    df['avg_price'] = ((s_price/s_area)*10000).astype(( j* H) X. x* y) g) A
                        'int').astype('string') + '元/平米'7 o( i- j9 j3 G3 [( G4 M" w3 `
    8 m3 A' L1 z, O1 ]; T
    df.head(3)
    2 V6 v  _$ z* {, O: qOut[167]: , R  w7 [) ?3 N4 @
       year    area   price   Level Highest  avg_price4 v) U  K2 z- v; o1 }; @
    0  1986  58.23㎡  155万    高层     6          26618元/平米
    ( }) T/ r5 N+ k* j  `1  2020     88㎡  155万    中层     20          17613元/平米" z. A. j- [+ J3 k1 T6 J
    2  2010  89.33㎡  365万    低层     28          40859元/平米
    ' P# \7 @- M% J3 w' Z" E1
    5 v! l1 V! J2 i. L9 \2
    : B& a( {+ s  ~. K' a# F: G  i33 |2 S/ G8 N& ~6 j4 F  G$ U  k' d
    4: q: p1 z' \' |4 s; i7 s9 v! ?5 _
    5
    ( z7 }: Y( \1 E  @. W6
    / m! _3 A/ \" C, K+ m  L9 [& H7! d' v( \4 r  u: y* c6 c
    8& x( K% k! d: Z: }. \% x
    9
    1 k- W! b8 c7 n% n10: [) p8 j! d5 X/ }
    119 r9 v1 H, }8 k+ {( ]
    12, i' o% R" t* x" R2 c6 w
    Ex2:《权力的游戏》剧本数据集3 K4 f, A) g/ [
    现有一份权力的游戏剧本数据集如下:
      O0 h, j4 ^2 q. J4 z
    3 d+ d; D* D* Sdf = pd.read_csv('../data/script.csv')' a1 n- X3 O# i3 r, ]
    df.head(3)# Y, p* M) X* j8 P/ U% S

    2 p2 s3 H- {8 yOut[115]:
    / |; `0 Y; H, h8 d/ V. OOut[117]:
    : O1 `( _. G1 s8 p' b4 D, e, k  Release Date    Season   Episode      Episode Title          Name                                           Sentence
    , ]" X  f8 q, p5 S0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...* A- q' X7 S0 d- j  w/ ]4 N
    1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...) X- i2 d3 a2 K) [$ E
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce ( u; J! l" j5 G" `8 n/ T5 l! n
    1
    . l' Q% v$ v; V/ n3 g2
    4 S5 S7 C7 I! S' p. P3
    9 \+ M3 P+ X$ u0 f/ R" r" S4
    3 L# ]% }# @' y5 n$ _59 e: g. G  ^, H* R2 U
    6* w+ g, g1 j3 I. @, |! J
    7: R8 _- p: p. ?) L. @
    8- L% i% |- c3 B
    9: D' f0 V0 d2 b0 p
    计算每一个Episode的台词条数。4 ]- K9 j: j2 U5 c1 L$ g- B/ D
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。$ C; g) U8 g8 A# G1 j
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。' w9 @8 n. {8 _( Q5 `5 Z5 E4 Y
    计算每一个Episode的台词条数。
    5 O3 V8 I8 `) `3 M9 m; p. q+ edf.columns =df.columns.str.strip() #  列名中有空格
    5 U6 }3 T2 l- y& X0 n5 Y2 xdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()1 e" T& k- c1 A& t8 r7 X# X5 h* S

    " ^% g2 B! l" Qseason    Episode  
    2 S) J, \6 d, w5 C) s  mSeason 7  Episode 5    5051 t  u3 ?9 j( q
    Season 3  Episode 2    480
    ) h" l" t0 c$ D4 `  B8 {* ySeason 4  Episode 1    4753 ]8 j$ I' f% y0 r& ~. m- Y
    Season 3  Episode 5    440
    0 k. r- |. J+ V* P! H5 {Season 2  Episode 2    432) s; F2 u/ J* k& L
    1
    6 B7 D+ Q& A3 w2
    : q8 ]* w5 L$ H* {3
    ! Q% m5 |& |% r  \' j) X7 r# M4$ Q3 T0 z5 u8 \4 V* u3 y0 s, \
    5+ U+ Y  g7 @: f% n5 k
    6
    - O/ J- C/ K" M. \5 N1 t7" F, Z7 p4 G  z6 i. i
    8+ F7 n% n4 b  M# v6 q; E% X
    9
    # e, l3 t/ c. \: b以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。, o6 v2 p* Y- }, O( u! W" N- U& G5 _
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
    1 U. |- Y; e; r+ Gdf['len_words']=df['Sentence'].str.count(r' ')+1) c4 |- A1 r2 p
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()# G' }  M. K# @6 P

    ( y5 k3 L/ ^3 C" ^+ Z' L  iName
    ' _9 v2 F( ~3 A6 o+ qmale singer          109.000000
    ) H5 I1 }- j' ]. t0 ~; Y+ v$ ?slave owner           77.000000
    , S5 T9 \& \2 R% Jmanderly              62.000000
    ) M" T0 Z3 e* d/ c" ^# l" Clollys stokeworth     62.000000
    3 R& N2 Y7 i1 v. {! _! P- B: ydothraki matron       56.666667
    ' q7 m4 I$ G6 U- W3 D. ~1 oName: len_words, dtype: float64
    ' ~2 t+ i3 b: l: z0 g, Q; W1; B& i+ f% \2 r" v0 t/ [
    2+ t' [& m/ x8 C: n9 y
    3
    / ?& C9 u  a, }8 r8 I4, v8 u! ^- j" R4 l/ ?
    5, C% r: G- X* x2 C/ o8 e! }" O
    6
    5 M6 W+ y3 Q" o$ v7: Q3 P8 L) Q$ `* A
    8
    & M  C  J! Q$ u- i8 R0 d9/ S; ]) N1 s' j7 a. ^9 J3 U
    10" N8 a; e% X2 Z  L8 y( e
    11
    ! a1 ]; ^6 |- S/ A* L+ [$ c  Y4 u若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。! \6 Y* l) u; r# n; x2 M
    df['Sentence'].str.count(r'\?') #  计算每人提问数
    * H4 N9 N: k7 I. o" n- L' B2 M: _ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
      n/ F' {: Y- ^9 D; y3 Mdel ls[23911] # 末行删去7 a' Q; o: e# G# U* ~2 X# I- k
    df['len_questions']=ls! X& V# x7 T4 H5 }" Y
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
    ! m. l. Z9 m( Q7 H4 z
    * e( G0 E7 q/ ?0 z5 a2 ]0 dName
    5 k& h: E% U' w; _2 etyrion lannister    527
    ! M0 f. V( _2 k0 O* |" Ijon snow            374- P$ h/ D1 q2 H0 }' P
    jaime lannister     283& e, f6 f6 H0 X* x0 B. I/ `
    arya stark          265) f$ W- C& E# L7 K' J
    cersei lannister    246
    - T9 w% G$ w. N" d* G$ [Name: len_questions, dtype: int64
    . E/ G) ?  F& u0 d$ A! U
    ; l  n* ^% @: A1 H) N9 q# 参考答案
    / r; u) l( N, l5 c0 s* U0 c* @3 Hs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    % P+ m1 V9 {! B! [9 X- O1 ts.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()1 D& B+ a+ U9 _* e  K! M

    . \7 K8 a! \  d8 ^4 ~7 `# {5 Q+ j1 O1
    , O$ P, G# G( X* g' F5 e2
    0 E$ t5 n2 c9 S6 u6 `% e3& Y, r( g2 ~. G+ H2 @( E% a
    4- `% E9 _6 N& Z1 G- I$ H, |9 ~7 @
    5# l$ g  l  M7 M% j5 }! p6 J/ w) n$ d( J
    6& q2 v! `+ u7 @9 ?1 s$ l. o
    7
    - c8 F' b" i% y, @& Z1 H/ D0 ~- F86 V" L% e: }2 `3 x7 ?; I1 b+ ~
    91 B5 q( T4 f, M* l
    10
    * [; `/ S* v+ m116 X* A! t+ v2 g/ |! d/ ]+ N4 n
    12
    8 Z2 \8 y0 W7 o2 u9 G) b13
    - b% [- A/ T1 d: k' y14
    7 i1 P* Z, S/ N+ [1 S$ n156 o' i, k' }% o* }( H/ ^* U
    16
    ! T. H8 G' N+ t* F1 i17
    / n# _- J. m! z6 [$ S第九章 分类数据
    7 \# D# g* i+ Z" u7 n5 l4 h1 \6 Vimport numpy as np$ N$ d2 Z& `% u4 t8 f" x1 c: J" x
    import pandas as pd
    : D' k8 {0 B* ~) P1+ T* C7 X! j! q
    2, @( L, V3 Z4 B$ p4 H
    9.1 cat对象7 g. D- S: j/ m
    9.1.1 cat对象的属性
    ; W" Z1 g' |& E" ?4 H& N& P# Z, G  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
    ! N( _- k) s8 Z7 z% g4 |' E- R6 S. B0 {; C; ]" Z
    df = pd.read_csv('data/learn_pandas.csv',
    - n; j- n9 u/ T1 R: |     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    ' c) D  ?" B7 u. j9 n' J' L  Ws = df.Grade.astype('category')# t* h& a8 t0 Z( B8 e
    5 N0 z1 u( \; [. ~1 _8 H$ ?! Z' k
    s.head()
    ; ?6 z( c6 U+ ^" d4 n$ `. \. lOut[5]:
    4 l( f2 A. ~+ e/ K9 ~2 o0     Freshman
    # r  y, h# f: U/ L6 e1 i2 @9 Y3 c  a1     Freshman
    ' G6 A4 ]4 h; S8 h. O$ b2       Senior
    - o6 ~* K% \4 v7 U7 c3    Sophomore
    $ a) g0 `- Y8 v9 _/ c4    Sophomore
    " r/ d# T7 g( o/ v0 ]0 qName: Grade, dtype: category* ?0 x) h9 H% \8 ]/ |0 A, ?. t
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    4 p' Y6 w$ n5 K5 W( e* r/ l1
    0 {- ^* i' O% ]; d25 b% y/ y+ l( Y% G: g, Z, V
    3/ F. C4 T1 A( L; W7 Y" d/ p! E* _# v& E: Z+ A
    4: b; r! Y; @8 ~2 R/ ^
    5" b* p' b3 S. o
    6
    * x8 V( z0 H( }6 i/ E7
    ; j4 n; D0 N& {- R' f8
    5 v6 c' X1 _; B3 k+ h% u' N9
    , J: u: L' S9 O10' m1 u5 R7 y( ^3 w
    110 e; d# u' t# f* P1 h
    128 K2 t$ e- X, G4 M9 Z
    138 y1 V% d$ `" D' X7 o
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。7 s  u' C% F: H, i" ~
    ' f& ?3 x3 M* H
    s.cat1 c9 i8 s$ t" c  x* C
    Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    ! ^( X# \6 @- C, V% z5 D6 F# v- J6 c0 x17 w2 J# Y1 S  g/ `/ i  q7 _
    2. @; c( q! r8 e9 L7 c; J
    cat的属性:9 W( P! p& l; p' z
    $ c" K6 m1 u+ C! L, i6 \; Z0 D
    cat.categories:查看类别的本身,它以Index类型存储* P: A/ }2 s6 Q" t0 {$ x- H, U% `
    cat.ordered:类别是否有序
    " f$ b; |8 h; Fcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序% k& }* g; j  x7 _1 v# Y+ O( N1 f( C8 |
    s.cat.categories
    : H+ z  J3 K2 Y% L$ N8 bOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')3 v. Q  k1 A2 M4 z4 c. }

    2 e$ X" \- k. r* G5 k9 W- @$ t2 os.cat.ordered
    9 a9 ^; E% e% a1 UOut[8]: False) F0 \5 I/ m5 D/ j7 }
    * k' X0 f4 f# b9 o- S! |5 F
    s.cat.codes.head()5 Z# \' E5 W: l  e4 u
    Out[9]:
    4 g* _& m9 h+ j0    0$ J7 a$ `' [" C9 A9 j- @
    1    0
    4 y6 f  V- m2 p  q2    2
    1 R$ D7 p( q' y9 @) X3    34 c' u* B6 y" }7 V9 c1 W: c  A
    4    3
    0 u( G7 A2 x4 C( z7 Gdtype: int87 H. p  n0 \+ s7 Y
    1
    2 {. ]$ T% ~) {3 ~$ N' l4 J21 d1 \6 b9 o7 T7 \( c' \7 g7 E
    3% R% F: g& z5 O  H. v: C. q( G
    4
    ; |# L. a1 j8 f2 p5 G59 g1 x" Q$ [% K3 v- N
    6. @( \- J0 T9 t/ a$ r8 R9 p) F
    7' n3 E: h) }) g
    8' e/ i9 k) k( M+ w. H! m1 X; J
    9, R" ?/ m2 r" d0 V* l# }
    10
    " b% W4 J6 u, `3 O11
    ; y# i3 p+ G+ {7 a! `9 e. t3 r9 G/ N123 u* }) Q7 W- C' |
    130 a; @2 x8 I8 Q7 t. s. k( q) ?
    141 T8 B, D& h* [- J5 a& B
    9.1.2 类别的增加、删除和修改
    " Y' `5 o. [  ^  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
    9 l( T, u6 T- [, z
    ; m. x( \# @/ G" H, ~: i& L+ b【NOTE】类别不得直接修改
    3 z' s: x8 b6 r" F, x; s& f" z在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    $ B; L7 L7 x& ~; x+ j, ]6 P% C4 n, H; [% M! J% k/ F6 X
    add_categories:增加类别
    3 j, K1 H8 A4 P1 \4 Ls = s.cat.add_categories('Graduate') # 增加一个毕业生类别" N1 o9 i, a8 T- [$ b, o" M; s
    s.cat.categories: l0 x! `: q* S# ~

    , K$ f  c. x! ]. X0 `# ?Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')8 y( m# a2 c) k7 g
    1
    6 {0 n- K" e( l1 L; E2% i+ @/ G( y( R+ k/ o
    32 ~. _1 L, k  d4 r" i
    4% _# s9 y& f0 i9 a" O% s% H
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    & r- Q! ?# k! M& i2 is = s.cat.remove_categories('Freshman')$ N2 s+ R; O" D  ^
    . M$ a1 u, a# C. U: [
    s.cat.categories
    , @) ?- q) Z) b5 k7 G& NOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object'). ~; D$ Q% w+ g! U! ]8 d  a, X
    : o. e; n4 Y. r  N1 S
    s.head()5 f) Q2 E& L" S. ?+ h6 E
    Out[14]: : t7 ^' C. p# f) W
    0          NaN
    ( V, E+ ^6 L+ G' k, @' m, o! a1          NaN
    ) t8 j1 z  y5 Q' b+ c2       Senior) @, [& |9 A& P/ @
    3    Sophomore
    : A+ {3 q+ g; Z6 F- b) U! }6 q# l4    Sophomore; C0 K/ V& w" e- ^* I( t, \8 {
    Name: Grade, dtype: category
    2 N2 S+ `" Y9 M% dCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']' Y$ s: A. e+ V
    1
    ) `- X! L9 ?$ F! K5 \/ c+ X" d2" p. t' q+ F( f6 ^
    3) e9 M2 C5 H  ?" `5 |
    4
    0 N3 a( }0 U+ z/ f57 B+ A+ t7 ]1 q8 x
    6
    ( w# G5 \# N2 k( @( O* I# L: F7  Y6 g5 Q6 b, _
    8" }. K  ^, P- B1 G5 S
    9
    8 O0 s8 @3 I! x; Y7 B9 _3 _1 K: n10
    0 }0 U* y% i( U( c& F- _8 A11
    9 u7 Q) T1 B% O4 V0 }12
    ; N( p% E: A* \2 N9 I13
    9 _* }6 s4 c4 R% @14% h8 S9 T8 Y$ q$ ^9 ]* M
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。4 p' V( p  a; {- ?" C
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士  _% S! `7 F! _" y
    s.cat.categories
    / E4 P- V8 J& W( w! QOut[16]: Index(['Sophomore', 'PhD'], dtype='object')
    / H: `; ]7 Y# w! O
    ( b; h$ I( L3 U" T& b8 S$ F9 a6 Vs.head(). Q* b4 t$ b% Z" c( b) \( I
    Out[17]:
    ) P/ p/ M, {' g0          NaN' p3 \% V5 y/ I" W# w5 D. ]
    1          NaN
    % [. h: w9 ~8 _0 x2          NaN1 P- r# h* R% U1 w3 f6 h7 s8 D
    3    Sophomore
    1 P; k0 U0 _) ^, u9 O+ u3 s% ?  L+ k4    Sophomore. Y3 {: S6 Q$ z7 n8 b
    Name: Grade, dtype: category" v5 ^; o8 r1 z
    Categories (2, object): ['Sophomore', 'PhD']$ I' M' j' B# \0 ~$ I8 o
    1
      |0 t) |1 {( m- b; K- R21 r/ @; `# `7 u$ b. J+ H- x
    3
    , V+ J* R, {, D3 z0 u4 w  H. ]44 a! I" o2 P' Q" W; R7 A6 N) t
    5
    6 s3 s' m* ]' \! x# W3 z6( ]/ U  A* p( z: N+ M7 [$ l" H
    7
    + `5 H9 }3 @$ `% H8
    ' `# i# A! `. y# L& B# M9
    3 n, m$ a* D% @10
    & v/ q3 p, L6 v% e2 L11
    / ^5 [7 m% T: M7 j7 V# T12
    * G5 m* z6 |4 \+ N& \: g130 M0 Y8 i' B2 n1 M# O$ u2 u
    remove_unused_categories:删除未出现在序列中的类别9 D% d$ Y2 }6 i7 b0 s" h/ O" Q
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别% {# K# V! G' j3 O8 B
    s.cat.categories) R8 [( @5 P1 ~1 ^
    : k# J' H. c1 Q6 J! c9 T  Q3 s
    Index(['Sophomore'], dtype='object')
    " d* x4 O9 r$ w+ j7 q1* D( N$ V0 W  N
    2
      T$ b9 q6 i9 `7 _6 z+ P3
    1 A1 o6 ]6 C# I4 ~4
    1 y7 W- X7 H- A& A8 crename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:9 T! e2 W" d2 ^- O. ^+ }0 w- Z  w
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})# I2 w+ n3 s+ }7 ]  ~
    s.head()
    & \$ `8 C# t" z" Q1 O7 n* W+ j4 u: A- h( D7 P& r
    0        NaN0 S* q# e2 h' ^, n  U/ n- `; D, D
    1        NaN' H) N$ `8 U) H, {1 @
    2        NaN
    6 q, w3 C0 O5 {  }- {( r3    本科二年级学生
    + {' `% G! D3 j  C  c$ f4    本科二年级学生% B& o# w7 C( H# J4 y! x& I- \
    Name: Grade, dtype: category. d( \: H: i9 h" l3 w8 Z+ [" S- T
    Categories (1, object): ['本科二年级学生']
    2 j7 I# z3 b" Z" l1
    4 H/ s- A* L/ V9 N3 M; S& v2
    . x/ e7 B( Z9 c7 b3
    : d' B6 V/ M' h8 \4
    ! ]. {/ E) @5 b4 }) N50 N/ S$ ]/ c9 q% a% T4 m) G
    6
    5 P; X" l# b4 B: w2 z77 R3 f! Y* X5 X# z- k( p3 C, b2 V
    8- O- m& Z5 ^  k  Z
    9
    & q, D+ G  X! [. f- Q6 {10  N% r- p& T! z0 ~0 {5 u- H
    9.2 有序分类
    - w4 X# |# j, u3 n" N0 C9.2.1 序的建立3 z4 g5 G, S9 a  n% J  B3 }
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:8 C8 \" v- \1 J  ^
    7 V5 C9 D7 i3 @2 H- o- \6 m9 W
    s = df.Grade.astype('category')9 q6 I! m; c8 T  Z! v: u
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',
    0 g1 y2 L1 G: y: o( H                              'Junior', 'Senior'],ordered=True): K3 T% t4 p" L$ W- N( o
    s.head()
    ) h* Y0 b1 r+ M4 f- aOut[24]: ( D7 y6 m7 _9 M9 m( ?9 u
    0     Freshman
    , @" \( `# R! G) T: P1 ]1     Freshman, r) p1 w, b" n* f2 _( ?
    2       Senior. A- y* Y* q! [6 q
    3    Sophomore
    ( b* n* D: y' B4 R/ h7 J4    Sophomore
    . i1 \9 E1 d' t! K8 P+ y! AName: Grade, dtype: category
    9 t" J$ [( \6 E' P* _3 PCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']1 l& F- D; u% S1 e/ m
    0 J4 e1 T/ f' x
    s.cat.as_unordered().head()
    * W% [4 M7 J$ U3 J$ N6 V6 X/ x" @  jOut[25]:
    $ u2 D/ V/ i3 S& u( A! M0     Freshman
    5 g4 Z( A. F* k% y2 J# R+ X1     Freshman) D8 S' {$ u+ v  Y
    2       Senior
    ! v( s' J& z/ v6 p: Z3    Sophomore
    8 A3 ]" ]- r: B, x' H: x4    Sophomore
    ' [6 ^) U2 n- ~Name: Grade, dtype: category
    9 @9 `8 N6 Q6 V, Q& p4 dCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']+ {: j0 `2 N; C8 v2 S0 |& [
    0 D" p3 d" a+ V) O
    1
    7 h) f: j$ Z) r9 @5 Z) g% c2
      g( O" a: t0 M/ c" m- v3! B! @7 H. x* l- v0 n7 [+ _
    4. O7 Y4 b( l. P' ?
    5
    6 l' k1 P* E2 A/ J: t  _# F6
    5 N0 z9 |# d4 [% ~7
    9 x9 V% G% E8 Q% [87 n: Y' k, y3 {0 r2 Z; G' T
    96 `7 o, p+ m: {& m
    10) }2 F  ?- @: U( e" W. E, b
    11
    ' U0 V; n. @* |7 ?% c12
    + S5 B0 D- ]) g# G: i133 c. W3 V* S& x$ J5 w7 a8 w3 j+ K
    14; \+ f& O! W7 J9 u! r
    15
    " q- s7 D2 j1 L$ K0 \16
    7 H2 t; z, {$ T& N* N17
    - i+ W; ?5 W1 P2 I* x: p4 p6 A' z7 z18- M2 X! B5 G% E+ Z8 S
    19* V( b! i8 h# U5 {* i  y% O
    20: G' v& A$ B( D
    213 |9 y0 M2 e' F
    22* U0 _- T# B4 W% S' y1 u  P/ _, t& P* C
      如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。. R1 D  X! `+ I

    / o, G( o1 p. m6 c; ^7 T9 b/ y9.2.2 排序和比较
    / q* ?- J: J( R在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    ! `2 f0 L4 I# v8 a: M& D% d/ J/ r4 F  p2 i+ a% @
      分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:' z3 ^1 `, T, D: _

    # B0 b# U9 E2 U; `' M- h8 r% qdf.Grade = df.Grade.astype('category'); T; T1 j& B9 j/ Q
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    # t. m9 S! [! E3 t/ vdf.sort_values('Grade').head() # 值排序
    $ S7 [' t  m3 V- ^/ B6 @7 A4 QOut[28]: % I- @& _: u5 H' P, C  I
            Grade           Name  Gender  Height  Weight1 R$ @1 M. x; h  o- k
    0    Freshman   Gaopeng Yang  Female   158.9    46.08 A! S  L1 D$ a/ E" ^% _1 S, B
    105  Freshman      Qiang Shi  Female   164.5    52.0" Z% D6 G6 r3 x; g! G6 N5 r* |
    96   Freshman  Changmei Feng  Female   163.8    56.0
    + g1 n% Z% E& l. [3 r$ b88   Freshman   Xiaopeng Han  Female   164.1    53.0
    ; q+ S: `6 r2 R9 _3 y/ y81   Freshman    Yanli Zhang  Female   165.1    52.0: s, i# ?4 G- }, Q" ]: h

    1 g& E" A, W+ z9 }df.set_index('Grade').sort_index().head() # 索引排序
    6 A0 k4 l7 V$ @* n/ i# {( }Out[29]:
    . I. R7 F; w% ]* J1 a0 K; D                   Name  Gender  Height  Weight/ y; ^3 a" p* B
    Grade                                          
    / G8 f5 Z7 Y' O+ E1 k% {& J) XFreshman   Gaopeng Yang  Female   158.9    46.0
    2 O! e+ R, V# jFreshman      Qiang Shi  Female   164.5    52.0" B9 f, g8 d; P9 [
    Freshman  Changmei Feng  Female   163.8    56.0' [( l# a% v3 k
    Freshman   Xiaopeng Han  Female   164.1    53.00 e7 i+ t) ?- u: I. t3 L
    Freshman    Yanli Zhang  Female   165.1    52.0
    % h" p3 k$ f# [. G! q# q8 ?
    7 ]% K0 e) @# [! Z4 Q) _1% P# E6 V+ j; e8 a( }1 K+ M
    2* E$ v. m: L' r! z
    3
    : H$ S& d/ L, z" H2 J4% W/ g" o! Z4 @
    51 m0 ^- S9 j1 w
    6
    8 d2 B) \& U% v6 k% W- t8 H" r% \7
    3 r) J- d0 o) H& ^* J8
    $ Z) s: s# x, T5 e% j95 o  @! {- \9 L: E
    10
    & R& K# t$ |* u5 h8 W5 Z2 g3 s11
    9 d3 q4 [, h# `8 s1 i; d12  z% q0 L0 Z( Y5 V: k
    13
    5 q& l( a( M* u2 D14' e8 y7 V7 p" C9 w3 o7 W! Y
    15* L$ n  P: P* Y, m. j
    16! x0 U5 M4 [, _1 n. u1 \% O
    17. P( h. A$ C9 ~; f8 \
    18
    4 t4 q2 d6 G# Z' i1 F# f# m19
    $ D% K8 b/ h7 c) l20
    2 g4 Q+ A) @( `8 m6 W& M1 W+ [5 g  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:# ^/ s, q1 w: S8 T7 \: C) Z
      s$ |6 Y: r( J3 z7 [, t; e. U' u
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    + G) m6 n/ V9 [0 e>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。4 H; ~1 k) f( ]: t6 z: ^) B+ w( O& g
    res1 = df.Grade == 'Sophomore'
    - X& }" z% J& G( g% o; ~3 k+ |, `* b9 `& M& f- m+ J: `+ u
    res1.head()! j+ q+ X& f8 X% q
    Out[31]:
    1 n' W6 Y7 T; p+ z, h! V0    False# Y5 m* X! ^9 Y, X* c# B- B
    1    False
    4 x- q( e0 B& x# }+ B2    False( B- C4 R9 o1 x+ ~
    3     True
    1 [5 `$ r7 a# E4 p7 C+ m4     True
    ( M+ o! g! J2 Z7 zName: Grade, dtype: bool
    4 o8 z- B/ `) N
    / H# r& T' e. B' tres2 = df.Grade == ['PhD']*df.shape[0]
    7 n1 H; A9 W" Y- R" F# H
    + e/ K* G0 a, R' ]/ V2 \, x$ Hres2.head()
    . y3 \4 \: `% ~* Y) k( a6 F, fOut[33]: 5 I( I8 m. g. p" k3 ^! v( q
    0    False; i" I/ j1 m. o' T( D
    1    False
    1 X# I! J$ e6 K! S2    False
    ' w* D: y# x" `3    False8 @# P" G# C0 {
    4    False
    & s) b( J$ Y% L2 Q; dName: Grade, dtype: bool! J0 ^" B$ j1 n* l

    9 _" g6 T5 u$ s0 G+ Mres3 = df.Grade <= 'Sophomore'8 |! L) X) l: W- y

    $ z# _$ Y7 \8 c. F9 _res3.head()
    7 g  Q  M8 e# q2 Q  P  f& z3 tOut[35]: + \& s- T4 ]5 t" M
    0     True
    ' n. k/ P; `$ M1     True* M, i7 C' @+ ]2 \
    2    False0 v% c# f( \* L0 h3 n
    3     True
    7 k! s; X  n% K9 \: j4     True5 [! m4 ^. Q- g
    Name: Grade, dtype: bool& s/ V1 v4 f( K
    * }" a: u% J. f! G/ N
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    6 O7 a9 N! n/ x1 c. c0 ares4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    7 S6 a$ j. L7 n6 [$ d  U; y+ _1 f" ^
    res4.head()
      N/ I: V. B0 l8 tOut[37]: " B- ]* \. ^; T+ K& ^: i
    0     True/ N* u( ]/ Y3 c
    1     True
    4 M( a6 B$ W  j* S2    False* t! m- s/ _+ p2 v5 X/ G+ K
    3     True
    7 T3 H' q  b. W$ s- e4     True9 t7 V% F7 [( q- |* W* ?" X
    Name: Grade, dtype: bool
    . P# l. K8 j5 ]! F$ N2 m7 s! B  b3 l# _! _6 e
    1
    5 M& s# H% o- Y" p8 _+ e6 H" c2
    7 a( L) \2 q! H3) R; F" g3 V9 y( D4 @
    44 u/ ]! r' i$ R$ q) z+ \3 b
    5# t; K7 X1 i) a, x, I
    6$ j$ {; x8 e: K1 i7 S" x5 w7 y! Z
    72 m3 }6 I9 ]# k& ~. I) Y9 c& A
    8# ^' N/ P" t" ~+ W6 G$ e
    97 D7 P7 H. K% {! L- W
    10
    / W* _; D7 P) v8 @11
    * i) x1 i( A( c* U12) R2 Y( @) @) W5 v6 }' P% `! |# n
    13
    3 Y: E4 _4 ~3 @# \14
    6 A% i9 R) L' Z2 g* f9 V" G% h15
    , g8 a0 k: z, z9 J3 ~0 H16  P1 \. n8 E: o8 |8 Q5 o. B
    179 T8 o: z' c) w# R
    18
    # z: L! F$ v2 B4 Z* p19
    1 @/ k0 }8 p1 @$ C6 k  [3 V; y. g20) b) U: N# U$ D* ?) k* Z5 e
    21' \; p8 v/ k8 X$ \2 g; X0 r4 J
    22
    % P: e, }1 }$ v2 e6 C23% i/ m; l; b6 }) Q/ f
    24
    9 j1 W& k: B6 o3 ]) Y25" A" h' {3 F* l; s" W+ O" {
    26
    ) g0 s( P4 b. d27
    / k$ G8 ~7 ~; e6 W$ p7 [7 O7 {7 ~28
    " Z. a6 e' p) i29
    2 D4 P: X) o2 M4 G9 u( t4 r306 q$ G9 L  F$ P, Y' ]
    31( K/ J. y" C* Y1 i" `. E3 |9 \
    32
    7 B3 [5 s( {; U( i335 w  s0 j% s; d& a: Z
    34
    : x2 w4 O1 z( O5 P35
    9 R. \: v& J6 e6 A$ K/ f: Y36; ?6 G, B- v: J/ ~* k
    37' d+ {% E1 [/ T
    38
    ! S0 @. N8 x- Q6 e9 r393 W, W: H) H, x; g
    40( i8 p/ X4 N/ z; j
    41
    ( [0 a$ R# x+ n/ q8 U' N42
    - f3 X8 D% p7 i+ T3 z! R  N3 z; s43
    # Z. F* K( I6 A3 a6 N- f: b44
    1 [( _2 s" S/ m' e/ h6 s" E9.3 区间类别
    , `3 l, \5 c, {3 U% c/ }9.3.1 利用cut和qcut进行区间构造
    , s# D: x. j2 M- [* Y( r  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。. Q' x) V$ z- Z) z( [

      ~' z5 e  D+ Y! c5 y/ @cut函数常用参数有:
    2 D7 b2 \3 a/ e' B# s; S: dbins:最重要的参数。5 _" A% N4 s3 n  G2 b
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    5 T, \# K1 ?+ w9 H; z$ C也可以传入列表,表示按指定区间分割点分割。
    ( e/ F/ o* N( u* @  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。+ y) I9 f; Y. n. E$ P6 s5 ^; W
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。- @0 y6 \# P/ q+ @

    + k8 f% P; r  O# V1 }s = pd.Series([1,2]), w& F$ S# |* V2 s6 T! k2 F
    # bin传入整数0 e9 s3 q; |: u: N7 f* o! m

    ' l; a/ _, r& O  kpd.cut(s, bins=2)
    6 i. ?1 H3 ?( iOut[39]:
    7 R: E. x( B& Y$ M0    (0.999, 1.5]* l+ `& x* H8 {; g: @$ w& x
    1      (1.5, 2.0]
    3 R+ P+ a1 P/ |% N' x4 }dtype: category/ U, C- }/ @4 L# U0 H4 {
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    6 L/ d# f/ {! @# f( E% n% y1 u) p; U
    pd.cut(s, bins=2, right=False)
    % o, N  P7 c  M6 }; @Out[40]:
    1 f$ t3 Y" S% g% w$ D- j$ d0      [1.0, 1.5)
    1 b+ M; S, E5 D2 C! S" h1    [1.5, 2.001)
    1 z' A+ [& x  t6 s8 ^dtype: category
    ; N: l" P+ {0 A0 ~Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    & v0 j# R- U# y9 R& s$ T6 I( _! b/ k( H
    * P' @( V6 o, X1 F0 F' ~6 B- u% J2 K/ k% U
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):
    6 }% L" y$ T* k  i8 dpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
    2 Q6 O4 d3 X! u$ R6 Q) LOut[41]: 9 y; a$ h. O' }$ E% [" n
    0    (-inf, 1.2]
    0 Q( O9 z7 Q' ?1     (1.8, 2.2]5 U* G" L! \9 i! ?4 q1 j9 N
    dtype: category
      G- i) W2 O5 h  @6 m( \Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
    ( z( n8 p& b3 S
    1 ^+ I  m7 h, z2 E# r% O5 R4 C1" A7 q1 b6 q* {4 o; r8 }
    2
    ' S1 x" w8 s. ]1 c6 M3
    5 \( I' ], o' \1 Z' S48 ?; Y* p; m* b: q
    56 [7 e. i8 H8 j8 z- p
    6
    + B0 q% M* ^) x* `7- S( ]9 ?* D2 V) k: q! C: x7 L2 f
    8
    4 @# P6 O: Y, o/ b9/ e" j  O3 M: m3 J4 E5 _
    10
    4 z" I/ u4 q# e* x- ]. X0 m110 B: x- m8 q5 @9 ~6 Z
    12* d. A- z2 o' E* }  p
    13
    5 g( O- L8 T0 D. x14
    " T: k( d& f! ]1 R" @- t, U15
    8 b0 X* a' o( F0 Z% a16
    ; }& x7 D! \. E! p17/ b1 G( I3 o( W
    18( n( M" @8 k7 F5 v( c5 F
    19$ c! E+ S$ G! O" M
    20
    5 l* ^" c1 t2 ~+ @/ E) `21
    ! W" Y& r* P5 c" {: S: b" f. r22
    ( b, X( J5 B1 K+ l# r2 O$ O23% Z4 T- b  d" d; Y3 N6 W
    24
    ! [* C+ x8 _: M9 V8 I25' n: i- I( Y1 u4 Z- z, W9 q
    labels:区间的名字
    / ]; }5 j7 i+ l! f0 Mretbins:是否返回分割点(默认不返回)8 d  H6 c( s( r  y1 u6 k) p
    默认retbins=Flase时,返回每个元素所属区间的列表2 ~: Z' J1 v5 z5 E' ^- q. E  e
    retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值$ w$ m+ ?( e' `0 ^

    6 N0 ]0 R& Q3 S. B5 l: T5 @- \: ys = df.Weight
      r0 }6 ]) ^4 c& S: Jres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)" `1 H3 @6 q5 u" C4 w) ]# O
    res[0][:2]
    + X! P' I1 B; B: F
    9 r& B2 \* Z' N1 l1 JOut[44]:
    : l/ J' s# _7 N9 `( E" a1 J8 j0    small1 q4 }1 ]* d. X3 {: ?& g
    1      big
    ) U& q8 x& e! B2 Kdtype: category
    ( l1 `, j; I+ F) T2 l1 l0 l2 sCategories (2, object): ['small' < 'big']
    4 B5 H8 c) w7 R% y0 ]& T+ K
    * [  B) \' q' t/ F, U2 z; Cres[1] # 该元素为返回的分割点
    ( p  V, w: @  c& G5 k  ROut[45]: array([0.999, 1.5  , 2.   ])
    0 B, }4 _; M6 _8 U- ~1
      Y) |$ Y8 B* W1 }4 Y2; j/ J3 L2 _6 N5 S
    3
    , C" U2 N3 U  {9 r6 G4. S3 c2 \$ }1 T  ~' Y# o
    5
    # Q, r! Q8 {/ f9 |( ]6, E0 g" J3 q6 R2 O5 Y: B
    7. X% o7 a" C+ H& j5 A. X
    8
    * n0 D+ I: v6 O+ T' v91 m$ m+ G: @7 }, l. S
    10& K7 ]: A  U9 D% c2 H
    11* ~$ G2 N. B8 i- p
    12) h' I" O) r$ P% t4 K
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    . V4 x- p7 c, p) Lq为整数n时,指按照n等分位数把数据分箱
    . B) z, H" f- O) E8 D3 Fq为浮点列表时,表示相应的分位数分割点。
    & }: L1 u! P# Ks = df.Weight
    + P# I- ?# v' Y  I. b$ D! {  ~2 C/ z3 q, M+ E
    pd.qcut(s, q=3).head()+ \' W* _$ R1 R/ h6 m
    Out[47]:
    ; Y8 i% f' Z/ `0    (33.999, 48.0]
    9 a; X- D+ q( x; N1      (55.0, 89.0]
    5 J8 ~. t; S- d2      (55.0, 89.0]% _: l" Y* c  f( d
    3    (33.999, 48.0], o) d7 z0 `) l% a$ S! D+ ]
    4      (55.0, 89.0]" ?9 b9 `) S" _+ T
    Name: Weight, dtype: category3 F5 b- G: W4 k9 G, k7 ^8 V
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]7 m% i! L, h+ [$ |) a( n, U
    " K4 m( ~$ P  E# x) m# B' s
    pd.qcut(s, q=[0,0.2,0.8,1]).head()
    % |4 f+ O! t' }, ~2 v) GOut[48]:
    " T. v" a1 \% e4 a3 }5 z0      (44.0, 69.4]/ s  d3 L1 |. i- ?
    1      (69.4, 89.0]& a$ x7 X% t  @9 Y8 p7 g. g6 y
    2      (69.4, 89.0]
    1 |# P% s/ z2 h  i' i3    (33.999, 44.0]7 ?4 p5 W4 e, H( ?' N; b+ H, Q
    4      (69.4, 89.0]
    9 U& B! a. T/ q# o/ F' ]6 V% UName: Weight, dtype: category
    8 q7 m! o6 W1 f6 e! r" ~) v. `Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    7 T) _) o. y* _! o
    3 V6 D& x) J. b( F, O! g# H1
    , J2 B7 x- t% ]$ I' o2 G- ^) t0 h2
    + ^* U& m5 Y  I! L6 h$ I! ]6 Q* G3
    9 J0 ?, l) n0 i) R# T* z44 _: @( X6 H& l3 E
    5
    7 H0 J8 t: t5 \! Q" s0 S  [6& P) I1 x3 v& F$ \4 j
    7
      b: k/ F8 Y/ m* F5 {9 _8
    5 M' o+ [7 P4 k+ d5 V9
    ; f+ X; y1 _/ Z' V& o/ t1 g! g109 O1 p0 ]+ [1 z, ^* a! [# S
    11
    / O9 g' Q5 B( \. A127 j# p5 w8 u! i' ~  L/ }
    13' k$ R- U' ?# t( n( T. J
    14
    9 S6 A5 `5 m9 t$ Z15$ L) y0 s+ u9 ?# G
    16
    7 j/ K# E7 v+ n6 y% L& ?17  C1 q  B! W2 o9 M' _1 r- ~
    18
    , e3 k; P1 I# Y* K* S3 S199 }8 m* k7 M' ^) M; X$ j# S
    20
    , y- _# A) c+ C5 G; q21
    & A) Q! d4 C/ O" M% c$ S2 S+ u2 C9.3.2 一般区间的构造
    % V' C1 u5 L/ o( h- V. F2 P: w! h  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。0 Y) c2 m7 c8 ?- r

    ! w* ]! w% v( i开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。) f8 l6 w9 h; b/ c
    my_interval = pd.Interval(0, 1, 'right')
    7 c  t$ [& H  F4 [, g0 |  m% T; g% y9 }; y0 C1 Q0 V
    my_interval
    $ y$ w) O" C+ R& E; y8 V, n3 HOut[50]: Interval(0, 1, closed='right')
    ) u8 x2 E: U6 |. g1 q1
    ( q9 _" b& x# G- Q) u+ q. W* X7 E( j2$ o# n5 x5 C8 \% ~: K9 f
    3
    5 F0 ^4 c. F5 f- T& R; A- h( i43 y& D- ^! {( Y8 W8 ]2 Y) Q- W
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    + o  O+ r3 ^8 k; z使用in可以判断元素是否属于区间
    ' }/ D0 b- ]5 z0 w5 ~: L用overlaps可以判断两个区间是否有交集:
    - \) J+ l8 b: H# M/ \$ |0.5 in my_interval
    $ J1 W' Z5 P) }0 |& Y8 U; H) M/ m: c6 l$ G0 W& M  L
    True
    - f; d; F: }" I7 n1
    # o5 K+ H) I. [6 B2
    ! W! O, l8 J- {8 O' D3
    * U4 L& B. h/ C2 C' I; f) v6 ]my_interval_2 = pd.Interval(0.5, 1.5, 'left'); m# c0 {# u: h$ }- z/ O# g6 p
    my_interval.overlaps(my_interval_2)
    5 Y( w5 s/ Z$ m6 @6 C2 U" D9 S" F; u1 q) d( i1 t
    True' r& x/ P! ~% N0 J2 E1 f
    1- O: v# }1 y" [  X4 Q4 f0 N2 R
    2" c# f; T% O. k8 E2 l+ Q" e
    3
    + g6 M8 A$ l; G% V1 g( H; l* J, k4
    , X) G+ D( _* B3 v5 @: u  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:, F. s! X8 J/ V1 v& Z' t

    $ m7 O: H6 E' b4 j: @8 X% Zfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    8 [4 R$ d% p' |* o8 Rpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')6 \5 n% k4 z9 t; g
    9 b; }6 m, `6 a2 s
    IntervalIndex([[1, 3], [3, 6], [6, 10]],
    & T1 i" \, e) {; C* b9 L# L) H               closed='both',
    + P. m0 M. [, q8 g' ]               dtype='interval[int64]')
    7 ?7 c3 K% q: p# ^+ d5 J8 @' U. F1
    : U% p% T2 Z9 m: o. V" s2( H) O" d; T0 }) c
    37 U+ [) _& j3 }; b& x
    4
    0 n8 I; o; j, W, `/ }# A5+ c( E2 A7 T. \7 O2 M: |
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:. f; ~. d, J6 O9 {# _
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    4 U1 l6 i* D( V2 R, C) Z$ ^2 n0 w: O3 E' ?
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    3 @9 q5 I9 o& ?. B, x' S- Y6 d                  closed='neither',' T- w( @6 E1 g, ^: _+ @
                      dtype='interval[int64]')
    + Y  V0 n0 }& [3 b$ O1# B& ^( R6 c- u9 x
    2
    / E: A9 {2 }+ @, q& ~39 l, r5 g! g! x( L1 Q3 C# \
    4
    6 F3 D% o  D0 r: D5
    4 ~( h6 u" e. |0 J( c% r9 z! dfrom_tuples:传入起点和终点元组构成的列表:
    - J2 `% j$ E% p  H2 ipd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    " L% ]5 v7 K" f. m; H- {
    4 Y* W& W9 B- C8 _5 A& CIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],1 V7 u. s1 l% _+ Y
                  closed='neither',
    ) g: y; B7 o1 I; @! Q/ z! M              dtype='interval[int64]')
    ) r) j2 f' C, R( p' U  R8 a1
    ( K9 ^- u" d% W3 [1 g2
    5 _7 Y/ x0 `8 E; {9 V, Y5 c. V3
    ; |+ Q/ z+ X. L( j9 G2 p' k4
    # d# `5 F! E1 U; Z55 B4 W" k' `+ z* r7 D/ z
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
    9 }. K7 O0 C8 a5 B2 o  j+ Apd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
    $ J; f+ q* U3 U) v- ~* `8 G9 uOut[57]:
    % i: s1 @, U, ^8 m% x3 zIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],4 w9 o9 ~+ s* \" i3 q
                  closed='right',
    + t: B# p7 k$ b' t* C3 k7 C              dtype='interval[float64]')
    3 D# i* y- k( [5 m+ ?- t1 e; p8 ~$ \+ `/ A- B, V2 f3 T, @& _
    pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度6 n% [$ j: O  T! E; [) T. W6 {
    Out[58]: 9 p0 t# g* ^4 L' Q  {' B4 I5 S
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],! `; v/ k( ~  L# p7 [
                  closed='right',
    " r+ Q# f/ e& l9 }: E              dtype='interval[float64]')
    + w# Z; d% ^" W* g/ P1
    0 a  F7 F; Z3 t( L# J27 v" p$ F% R. u& n; U6 X
    3  V! S( v5 u. d
    4* w+ T- s, H' f) U
    5
    - G' n7 u9 l( Y' o: V9 _( O) X6# ]# m2 b  }2 u7 s
    7  |+ w3 W, X( r7 d/ a
    85 A4 ?/ p. Z; _& `
    98 I( u2 W& G% J0 k4 N6 v- c
    10
    5 q) S6 y; H8 y119 }9 A* Q& I9 G4 T* N
    【练一练】
    7 m0 B1 M6 U" z) \  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。' f, y8 P# ~& x6 s) R3 X

    6 [( s9 n" `, o+ P( U! h5 E  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。1 p" S/ Y* i: x0 N+ R

    4 ?4 l3 ?' g& B2 n9 q4 [' y4 Qmy_interval) |- V7 ^5 J) n
    Out[59]: Interval(0, 1, closed='right'); {& D& b/ W# M1 a- {! K0 U

    6 a3 J$ c# f0 P8 tmy_interval_2
    8 O* s# Q1 _8 p' Z4 |Out[60]: Interval(0.5, 1.5, closed='left')
    7 s; u& H+ N% W& W# F* |  H; e8 m+ i* b% U: k9 J3 m
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')
    0 `8 A3 Y5 k7 C' a3 d7 zOut[61]:
    3 M1 x6 I4 k# y3 a+ R2 x% Y$ q1 s3 k& vIntervalIndex([[0.0, 1.0), [0.5, 1.5)],* o2 C7 z! j# m& c, x& N: `
                  closed='left',; S3 b# N5 g8 ]4 J
                  dtype='interval[float64]')
    % m" M9 |7 U0 B/ t+ X12 N  u% j8 m" q
    2
    - `: m& t/ O$ Q3
    2 r* ?/ r, U( }# s$ r$ C4" j3 H0 d( Y6 I8 q
    5
    $ L/ }. d) c0 J. v. z  @) ~6
    ( g  I- D) _+ O/ }7
    1 j) [6 a$ f$ w7 ~: c8 j# N8% @& W0 _! x0 `" F7 n
    9
    . Y8 [+ ~5 C* r5 D, T10. ^( c% A! v3 A& j) Y" p1 L) N2 p
    11# l( J$ s2 H; u% v
    9.3.3 区间的属性与方法
    / w; t9 e% v4 Q5 g9 k+ i8 V. v% G5 Y  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    3 u! h' ^: ?( w. z
    0 s* a# Q$ b7 h2 \4 u. c0 _s=df.Weight% i- l- p1 f& R" h
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    9 A; J5 u. F$ k3 n) j. sid_interval[:3]
    4 o2 J5 E) t9 X; Z# f3 c: }; a5 _- j* i" ]5 J
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    # `8 e1 O5 p' k& p' A4 d, S  x                 closed='right',4 @* V/ Q1 s. W; V4 y! \( i3 s
                     name='Weight',
    $ m  B$ g, z; H/ w                 dtype='interval[float64]')7 |3 Z" ^9 K. f: f- }
    1) U  N4 \  L! F& V6 i+ s: U; X
    2
    1 \0 P- l4 r; Y8 ?* {% r6 p3
    5 H! t4 Q1 B/ R1 [" u, e4* {6 G. u1 a/ B7 v8 r- }. I/ u
    5
    ( n9 a- u$ p) [, b, H( x3 u6
    , k7 f) r  p6 b. F7 L7
    1 t$ Y+ B: I3 O8- x$ D' R( G/ T+ w0 J; E
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    # S% [: f6 N% M& D: Vid_demo = id_interval[:5] # 选出前5个展示
    ) X; k7 j, g6 z3 b2 ]) f  n
    4 B, k- A5 l5 u- F, z/ N2 rid_demo4 v- f& \: T- s/ w, J
    Out[64]:
    3 \3 \# Y+ p6 eIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],/ ]9 h1 e/ b. K
                  closed='right',) ]! i+ D  \  m# j% c$ _/ F% S
                  name='Weight',
    # M! c' i2 C1 |! F# D# v0 M              dtype='interval[float64]')
    # \* q! l" M5 r. ^/ `0 y/ e: U% B: _0 f( U6 X
    id_demo.left # 获取这五个区间的左端点7 {7 {. ]: _' @  r
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    ( @6 X! h2 f3 v2 d: M: p
    9 L7 r9 X6 M0 {4 p! P' N3 Lid_demo.right # 获取这五个区间的右端点. p; Q! ]. {0 U: G- r8 U
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')' k4 ^3 k: x5 ~$ V0 T  p* V

    ! z! s1 [5 j, P* Tid_demo.mid
    9 v4 a8 `8 c5 ]/ g6 e* E. L! T4 rOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')" n! \% V; Q) T& h' t% H& }

    3 l& ^2 z1 j: b% {id_demo.length
    : f0 a; o7 s  tOut[68]: ' |6 _# ?8 f, q; F2 J' U
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,
    + l1 U8 }+ R% t- N5 E              18.387999999999998, 18.333],
    9 a, [9 G# q% M# a- i* y             dtype='float64')+ L. K$ X! S% j& y

    ' [7 m" P' x4 {8 g2 j12 d' y1 d' ?3 Y2 o6 I2 O
    2
    * R2 O, ]0 j0 v& Y# v3 }* m4 ?3 l" V# a0 s3
      W- I+ p. ~8 Y& D( T4 w, v  e3 q4
    9 R" w2 J& `' E9 m' U" W; M' S" [& \% L9 b5( C  G; k. W) b
    6
    & M, u2 T( }$ r' a" s# K7% q7 W- q: U# ?- V
    8
    + J: @4 T* c1 u9
    + u  b9 H7 `5 W0 d1 p10$ y0 h9 q5 r! J8 Q) n0 r& [
    11$ p; b* o( w0 u+ z7 s) V
    12
    ' M+ {8 W, ]& \" I' Y% X$ Z13
      m0 {: u" X, `) Q( p0 u14+ C; V" y% O3 Z( t
    15% M' R4 c- J. x" q
    16
    4 |. {% T+ i; d. H$ ^" R17. k% }* d& k( [& J+ K- }
    18
    ' x& }; p% V0 ]/ b. v: i+ {+ B. E$ c19
    % A* |; i9 U+ d: _6 J% e20
    * e  |! ~2 x2 R+ [5 o$ W: `21
    ; {! @. e; M1 e8 i" `, M22
    1 _: G" c9 W) E/ `0 B23
    8 \; s+ B9 D6 I; hIntervalIndex还有两个常用方法:* M, N6 X3 v. e& {
    contains:逐个判断每个区间是否包含某元素
    7 P+ d7 `' @8 ~+ v, u* [# Eoverlaps:是否和一个pd.Interval对象有交集。
    ; B+ `4 e' A8 k& D( q: i* S" {+ _& tid_demo.contains(50)
    * q! @- f8 j# q  JOut[69]: array([ True, False, False,  True, False])
    ! I1 `3 `/ {3 u# Q2 R1 w/ r
    - u8 ?$ @; m! E0 d4 p& `id_demo.overlaps(pd.Interval(40,60))
    ! n2 j+ a" e5 v4 H; |+ ZOut[70]: array([ True,  True, False,  True, False])
    9 v  v1 J& }0 s, A- P( i1
    . r0 O* K$ B( K2
    7 N% s4 `9 ]6 {0 r7 }7 o! h37 t' i9 `* S% f
    4! v/ o2 J  j0 J) ]* v8 w
    5+ L3 z1 r9 W+ t- y7 m  m& p
    9.4 练习" j7 s- X, S3 N
    Ex1: 统计未出现的类别
    4 J# ]# l, h  O% O, M% g) B/ c& U8 q  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    8 f6 D9 O0 r) O; P& v4 u' O; t) o
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
    . _- X+ \( o3 ]7 Xpd.crosstab(df.A, df.B)
    ; F' G+ q' M# X
    4 E# h  T* [+ ?8 ?  ~; iOut[72]: 4 I. ?2 d& f+ f/ a5 L
    B  cat  dog
    , I  I: S- @# J$ q$ UA         
    * \, |( G3 P* Sa    2    0
    7 }4 [' @' `$ _" ]) @: z! C( Bb    1    0* z$ Z5 c7 N. g/ M5 q  d" s5 u
    c    0    1
    - h$ g( K# U8 o5 _% X5 O" z: u9 Y1
    3 d7 a* }9 {* ?# C# u2" T0 }* `4 ~7 |; b! `+ T  E" f
    3, q. e' @2 O2 `/ i& K* J' A
    4+ k6 Y) X5 K- U% z) q! f  B. K
    58 K! a6 h* O+ Q- f5 {' o9 a
    6
    - B# M/ Z# K& D5 U6 B3 d# \7
    7 Y6 [! W2 c4 U: n7 E4 w8$ F% Z' b* y3 x9 F$ E
    9) {+ {. Z( |7 u$ N( I
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:0 T1 z1 [+ X% p( o/ R+ W
    , ^* ?# L4 F- d- y- r5 c
    df.B = df.B.astype('category').cat.add_categories('sheep')
    1 ]* h6 h2 c6 ~/ |pd.crosstab(df.A, df.B, dropna=False)
    / C7 Y4 M0 G$ E/ F  w- E' r' Y8 y9 W2 l/ O$ s8 H3 y
    Out[74]:
    & C1 D3 d+ N) t2 e1 uB  cat  dog  sheep
    ; O3 F* n% o! k3 Q4 q% Z- PA                 
    8 _9 |; [) y7 t3 Ua    2    0      0* }! z% y6 d. ~! T6 g: p
    b    1    0      0( u. w* p) Z2 q+ x
    c    0    1      0
    " T( j" a0 J- A( Q/ ]4 ~, ~8 Y/ [11 _0 l  E/ X( ^  i+ y9 x
    2
    3 d0 z6 o( J9 e2 t$ {9 Z3
    ( b. F- ?/ X& ?  D7 e4
    ' C4 A. Y2 `# V4 S  ]! D5 U: x5
    7 S0 w; L, f6 {0 [" T, m6! D/ ~1 i* }3 E% |
    7
    : Q) j" b# L' l8, a, h* S; Q* U/ \% C" a
    94 G. L! i$ j9 v+ a  \
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。3 a7 w+ N3 k5 @, N
    % {! i. G: N& P9 d% C- N
    Ex2: 钻石数据集# ~! i* v! t& H: |" n' T, a& [
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    % }9 d3 X0 j$ B/ s0 _) B  a: w, J
    df = pd.read_csv('../data/diamonds.csv')
    7 Z$ L( \# [1 [- adf.head(3)
    % U- K/ _2 K3 M; s2 e! Y# ^. L  j7 Q% ]% r+ S3 w: Q
    Out[76]: ! T; y1 x% ~1 @  M) |7 V
       carat      cut    clarity  price
    / q5 r' U/ N5 U" f2 m1 i. n0   0.23     Ideal     SI2     3263 S) U; Y$ V% h# _* g- U1 {. C
    1   0.21    Premium    SI1     326
    3 _- S" y* t. f7 D* C: ^2   0.23     Good      VS1     327
    * ]8 J8 J. t$ {& }1
    : T5 {5 y- J5 L, Y1 D( o+ j5 G20 h) d8 G9 f8 Z: {
    3
    2 r) O9 y8 |) {4
    ' b7 V/ k/ A, R7 v/ _56 U: m, F! W5 Q6 {  D& w
    6
    ! Q5 m2 @9 i7 _3 l4 L( R7
    6 r4 y! Z) C2 l: r% c: [8
    - m' N0 ^$ R0 v* k: e分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    . w) Y# a0 g5 ~# L& r6 x钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。  n& w& T+ ~* K4 z1 x5 M( e! w; `
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。, C( z+ o& ?& |5 {9 f+ ~
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。/ x1 T6 ?& a$ i% [( Y; I
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    0 L6 k, a4 g7 j1 k' q; l& L# ^! ^对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    + ?, y& n6 v: U% d  E. o& F先看看数据结构:2 x5 j; {- k2 m" T  g6 n+ r, V
    & A2 t9 w) R. b  R2 z- i9 J; e
    df.info()+ t$ j# |1 b  [% y, V8 z, n
    Data columns (total 4 columns):
    5 ?, d% L8 q& T+ h5 c8 [$ d2 o #   Column   Non-Null Count  Dtype  
    0 E3 ]9 M) h. b2 p- t5 x( N! a---  ------   --------------  -----  * a0 I$ ]5 H/ g9 ^& u8 R
    0   carat    53940 non-null  float64
    ( |- z7 b* j+ N 1   cut      53940 non-null  object
    " ~& ?& p' |7 R- l 2   clarity  53940 non-null  object 7 q- N2 }- Q; L% q# |* v
    3   price    53940 non-null  int64  
    ! s; K" h$ U$ l6 {' r3 X4 Ndtypes: float64(1), int64(1), object(2)
    % T' T0 l, }8 d$ n9 F$ A13 |8 h5 N* e# h6 g, c
    2
    8 ?# w6 v" X0 q3
    2 x) B: q) [9 K2 _$ a4
    1 t* H+ G1 o* m9 Z) F, O( R5
    9 }" k4 G3 d/ V4 u& i6
    $ n, k( D/ j; O2 ]9 V- I7, @6 J5 E( z# T) B1 ?
    8
    2 _: p. }/ `0 j* g6 y94 ~6 p/ n# Y% S- m
    比较两种操作的性能
    9 E; b- N9 L( G$ t' h8 r# O* S%time df.cut.unique()8 y, D: a/ `) O1 R6 i# W0 i

      B7 r2 N/ ^% y( I7 B. [Wall time: 5.98 ms3 S$ h, p  t" S4 `
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)1 L3 I( S5 u& N6 ]
    17 H& W- ^6 g) _% L
    2. j3 Q3 C9 ~% X# g
    3
    $ Z0 D6 E; Z4 K, T: x4$ P: S$ q; @8 x7 d0 Q+ c6 T
    %time df.cut.astype('category').unique()
    0 z: e4 H0 t' D1 C  W9 u8 w) U$ j( _9 L4 o  M3 B9 u* o
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms5 l9 h' j0 W0 O! y9 y% [
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    # g0 k4 r$ J$ K5 _+ [Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']4 C0 t7 H8 P4 O& I
    1; r$ ?; G0 b' X0 N. v
    2, `# g+ e9 D! o  |+ H
    3% G* g% {6 i7 l; r
    4# E# ?/ _1 u; e1 W( d4 F' v- w
    5; {+ m; ~3 Z4 ?% w) m
    df.cut=df.cut.astype('category')7 H: w2 S  |* U/ F& d! W6 W
    %time df.cut.unique() # 类别属性统计,2ms
    ; K% H# n1 p/ r+ f9 [. a% \+ c) U& i" e
    Wall time: 2 ms
    ' B& J& q( x! A" L" i['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    0 @2 C) h8 n. w( V% ^0 QCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    1 O3 A1 G8 y, g" b" V: v+ F1
    5 G7 c0 e7 T8 \' z, b2, ^9 L) K5 y$ f$ D1 V; N
    3/ U& d1 O! @$ x0 Y
    4
      u9 ]1 F- a1 N2 b% G# k- B; R5; b  u) R) h6 d+ B
    69 J6 V  X* |: g5 Y5 d
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。! o$ X" [9 H" F; f; l) Y. U8 h6 E' ^6 D
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']# n9 T6 S2 a( n. ~
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']- Z& s* r! x( _+ \! V( c
    df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换1 [( A2 H$ x' d6 v8 y  {
    df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True); x  j: L% k/ _* M; {5 h

    - ^+ P) C5 C' t! ^. e2 Pdf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    7 l  W) I* g4 H- J6 L  }7 K1 q2 m! ~; U
            carat         cut        clarity        price
    ' z: _& [+ J7 h9 c315        0.96        Ideal          I1        2801
    , \0 Q1 [2 t: x& `! L, n  G535        0.96        Ideal          I1        2826" t; t  p' i8 u6 Z5 T+ k
    551        0.97        Ideal          I1        28308 O* @4 U. [8 b7 ^! l
    1
    ( P1 C3 D  ^! ^* P3 J* X: V( @- S2
    # K/ D% U& I( M4 J3
    6 k$ h9 ]* @- [. j# l4 p: J4
    ' Z( p( j. M& V4 F54 b" T4 l9 i/ I2 W) X( V% b
    6
    / p' A6 x8 f# G+ [3 l8 z7. a$ \% p1 a; c: a; u
    8
      m! F2 ?" E( X+ J+ t# R9
    7 `& N* K/ i' F& l& [3 m& Y10
    . `6 n- ~; _, Z& Q5 b# u11  V: D: c+ X! l( G* B( }& q
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    + c0 a( p8 h) _' @! t) z) c# 第一种是将类别重命名为整数% r* K3 n: ?0 |7 B- |7 g
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))* {$ [9 k. k) _+ f
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)])): i8 U# z# Z& k; g4 c& n
    1 s" q" ~: x% L3 n- |
    df.cut=df.cut.cat.rename_categories(dict1)" _  U* i' t" a6 ]$ i
    df.clarity=df.clarity.cat.rename_categories(dict2)
    5 W2 r' I7 W3 r' Wdf.head(3)
    & m* j8 e' C2 b. i4 _2 C. j- T( K; C0 ?* o; h: }
            carat        cut        clarity        price
    5 u2 R! e' S. r# S- D( i2 }) R0 i0        0.23        0          6                3262 \* O$ ~0 ?/ t1 L+ s: S
    1        0.21        1          5                326; V/ Z- _# _6 {5 a5 _
    2        0.23        3          3                327  S# I2 e* z1 y4 B% O3 \$ |
    15 k; e  }. m; o9 |" S( k+ O
    22 X$ b( }0 K* k- i! l6 ~
    3
    ; e: [* ?- p4 T& N5 }+ E4
    9 S3 M+ K( l2 y9 {5
    % Q( y$ H5 I9 ^* Y: H- {# N69 K. j8 \7 C/ ?* N2 v8 f
    7
    ; ?% H! s3 i1 h7 ]9 |8# ^% n! n# Y6 |: K( v
    9
    0 m2 F# v: ]- g' H10
    6 Q! q* |. Q1 R5 {8 T. j# O11
    ) {6 J  b- ]( K7 z, e+ _9 R123 b3 r1 Y$ I1 r& e  m$ h
    # 第二种应该是报错object属性,然后直接进行替换- H/ i, R3 P$ W# a4 Y
    df = pd.read_csv('data/diamonds.csv')% Z. e) N/ }" W3 w) v, e3 u6 K
    for i,j in enumerate(ls_cut[::-1]):
      y8 d4 z" A. Q4 k( V& R    df.loc[df.cut==j,'cut']=i   Z/ X4 T$ @" n$ G  _+ r& z8 {

    / e4 z: A* s. w& V) N" dfor k,l in enumerate(ls_clarity[::-1]):8 A" q& M3 H1 l! [
        df.loc[df.clarity==l,'clarity']=k
    2 R0 l! O  k+ ?4 pdf.head(3). }/ i) {+ g5 `7 k+ A" u1 \

    7 e" S  Y3 Q5 N( M: I, j        carat        cut        clarity        price
    , W/ m% z4 ~, x  t; g; k4 @0        0.23        0          6                326
    0 ^( j3 J% r2 _/ o1        0.21        1          5                326- }- I1 S# a' b; Z$ \" y
    2        0.23        3          3                3270 ?' @' C2 @) n% V+ Z2 l3 ]
    1% T- R  N; Q) Z6 O) U1 V" Q2 m& _
    22 \1 A5 |5 [5 {" F! H# k' j- O
    3
    + G5 q6 X6 T7 O0 X: O' p9 i44 t; T9 z7 K8 b# b
    51 U1 u! y4 M% k4 m
    6# X( a; \( y  U- q9 G% ^
    7. m8 M* f' L( j1 U; B2 R; H
    8
    7 d% M% r: ?% P9: i5 O. _. t# g4 k) F
    109 G4 x% @9 Q5 e' [1 M
    119 l/ o- F) V) G& E( ~0 O( U3 U% C
    12
    5 k( a2 d9 Z) E$ V13' P/ Y  j) w+ f% [1 {& Y
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    6 H; k+ e; M+ C# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点. b9 g4 V( d1 @, \% m5 ?
    avg=df.price/df.carat+ [( E( ^5 N. d% |
    6 x& `$ M6 n! s& ^  [! A& n
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    ) m+ t5 p9 D0 }* L5 h+ g                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ; {5 z* b/ x5 S$ _% R2 {2 b
    * |. v3 R! @" W' bdf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    9 V0 E) l) b5 m7 w7 n                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]; p/ X# O" Z  T, j: Y
    df.head()
    4 D8 u2 v2 I2 \  U  R$ ~
    * ?2 g# M0 a6 {) Z        carat        cut         clarity        price        price_quantile        price_list& l7 G6 C' u( g% P/ \
    0        0.23        0                6                326                        Very Low                Low
    + N% V3 J, ?* n5 q1        0.21        1                5                326                        Very Low                Low
      Y) s7 L. s# i5 M# q2        0.23        3                3                327                        Very Low                Low( Q2 t0 ]* G' U& S( ]
    3        0.29        1                4                334                        Very Low                Low$ T1 m+ O$ e, n0 }" K/ ^& d4 u! G; k
    4        0.31        3                6                335                        Very Low                Low                                       ' g$ }1 i1 d( T  l

    : n& r9 J4 q0 X- k" t* H+ P1
    * G4 U; M' N( z  }, m0 O9 V2/ q" u% j+ Z7 p4 P' L
    3
    / n3 z- u) {$ Q9 S, V* w7 V6 |4( Y8 O" I" a4 n* J5 P
    5) t0 ]* C# L5 T8 k8 g
    6. e* Q+ X1 k: _
    7  o) F. W2 h6 E! ~0 C5 F
    8
    $ y4 V0 C4 [; _) R9, m) ]8 B# D: ]4 a- C$ k! h
    10+ O$ G9 r: v& r. A1 `
    11* V6 S4 ?7 ?& L) n
    12
    " x1 ]# K2 U) R& A8 D; R6 X135 w1 s) O8 S. l; f
    14
    $ f& Y8 t% k; K& x1 g7 s15  E7 A6 v6 f6 ^& Q4 J
    16% }2 |+ l0 a$ C, r- a
    分割点分别是:
    2 L$ {; }. U; A3 Q1 I$ P% k; K
    5 M+ _1 o- H$ ?, Garray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
    % A7 ~/ C4 m1 {array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf]), j5 v& C( s& G
    1
    9 H- ?4 ?; F9 M2 E2
    - v* K# r9 d# X第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    2 t; q' e* v: D4 t4 w! t/ L! t, W3 e! ?df['price_list'].cat.categories # 原先设定的类别数
    5 R7 j$ k3 V/ V$ B6 U. A- @/ IIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
    2 ~0 G* P3 W) O5 l5 J- H; b2 J' ~* P3 V
    df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    % ?; u8 Y) d' R0 D% i5 DIndex(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现1 O, f! L$ ^. l6 W2 s
    12 P2 Y  I# f/ G! T: n: v
    2$ y( T! @" X( Y2 X- O
    34 {$ A% i6 ]& P9 v4 l( x
    4) v: }! M! e  c1 U8 w" V( w
    5
    * N& ?; N1 d* p) |avg.sort_values() # 可见首尾区间确实是没有的6 P" Z3 l  x: M7 l
    31962     1051.162791
    0 Q7 O4 T& a6 A/ w6 a15        1078.125000
    7 ]! b* t: P9 s! @! M; a4         1080.645161( ^7 |& `% P! J6 t4 w$ l
    28285     1109.090909! P3 b. a* k# w& d) L/ C  z
    13        1109.677419$ b) v: Y! V* p" y3 c  m
                 ...     % f8 n( V2 U& X( k/ I
    26998    16764.7058829 g. q9 @( r; @9 B4 u! f
    27457    16928.971963, e7 `9 ^2 X) A% k
    27226    17077.669903
    $ j" g8 j  U/ V# G/ X+ d! }/ F27530    17083.177570: ]: {: n7 K2 H( C# n; j: Z; F
    27635    17828.846154+ E) M2 R8 B6 m. O& S4 J$ Z
    1
    1 U$ V7 P+ K2 R& z5 N6 u0 R/ L' @2" p5 `  p8 A3 X9 s
    3
    " x  j- c; f8 w% Y% [& c( a4
    , ]% h8 R. O+ u3 U4 N0 f5+ k: ^" v, B  Z: k
    6
    7 a/ C* w% n/ [' }. _7 [$ g7
    . N2 A9 }4 _/ x& c7 s82 q% K" @" Z$ K/ a9 E0 t, b
    9) Y: O2 i. `7 C1 A
    107 c1 H/ P9 D7 |' B+ `. e
    11
    & V, k: p2 G! i" i" b12  u0 g2 w5 a( {4 }1 f0 z7 F) `
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。, q) G8 `. K5 |) E; k
    # 分割时区间不能有命名,否则字符串传入错误。
    , g' u( }9 {# Y) sid_interval=pd.IntervalIndex(  `; Q- n( a- A2 N8 G
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]( x8 |% |4 W. J( I4 _: D$ ^
                                )
    ' `1 j5 a  u6 yid_interval.left( P9 A1 B! [0 L6 s
    id_interval.right
    # c  [' u: w3 Iid_interval.length                           
    " _. s5 _) C$ b. e9 {' D+ k/ \( }1
    ) {+ _/ O& I' ]1 h) N) i$ F2
    4 q" [1 H+ Y4 P, H" T6 V34 m7 B  I  b! D
    4/ j# {9 ~' e0 w( S2 s+ I
    5
    & K7 r' F3 [( D6
    % O+ U& Y& H9 s5 B( W/ O  s7
    # P- c4 E6 c$ Z- ]" X0 Z$ b第十章 时序数据
    5 P* ?7 f; z5 Uimport numpy as np& R. l8 e- ]7 c7 w  h2 c/ y
    import pandas as pd
      [# n8 v& }1 Q: }" A# G+ ^/ b5 j- @1/ v& r, z% u4 }9 x/ Z
    2
    1 ^5 ]/ j! q: W- x, R& ^
    $ Y, r- O: Y& p0 t. i3 U0 L& ?% @  r) B
    10.1 时序中的基本对象: p$ Y2 Y6 q( W/ D9 i; B
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?/ \; E( g; @1 P6 f% g- ^
    % q/ ~4 Q1 s3 O" ?: c( c
    会出现时间戳(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的简写。
    . v7 t+ _) S5 u' W& u+ @( X; B9 N
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。- e8 c- ?% u) P  D0 U0 A3 _; Q
    + v3 \/ W9 {5 |1 m% f  Q5 ?* r
    会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。0 Z% L7 X  s, ~4 w; d1 j

    5 L+ X- {" O4 C! F8 b8 V7 o& J会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    9 |* P* |1 U) {6 e  U( q6 P/ k) P, W1 X8 L0 i: ~7 j
      通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    ( e# X8 ^" `5 v8 M5 v3 ?0 f6 z8 D3 I3 ^5 K1 d( ]" R
    概念        单元素类型        数组类型        pandas数据类型
    + c' a# d/ v0 }- w: B7 U2 M) rDate times        Timestamp        DatetimeIndex        datetime64[ns]
    $ |- k6 M# H* g1 E% o0 n4 E. [Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]- Q3 r$ B: U$ c, \8 o5 e4 d
    Time spans        Period        PeriodIndex        period[freq]4 ~% H" w5 ^$ o1 Y) Z
    Date offsets        DateOffset        None        None
    " _; \: K$ ?- U$ q; J  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    / Z* @; s+ g' i6 B
    4 |1 {2 Z2 k6 U" }( d10.2 时间戳" A9 Y3 Z* J2 m* J* ]
    10.2.1 Timestamp的构造与属性
    / Z0 v+ ]  [5 Z6 @0 Z9 {$ c单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:+ C4 P* n. E( G5 ]$ ]+ D! ]
    6 U2 j$ j& q' X( m- q) f7 ]
    ts = pd.Timestamp('2020/1/1')) g( w3 ], Z9 Y+ M

    9 ?- a% u/ R' E1 M' T& Gts5 }: q, ]5 Z: J* c$ d- `. f4 E
    Out[4]: Timestamp('2020-01-01 00:00:00')  ?* \$ r0 s) n2 N, V

    6 n9 {& ?1 E9 i3 Nts = pd.Timestamp('2020-1-1 08:10:30')" c' o3 J7 E. S+ L
    6 {, i. w% r  P5 Q' h4 u
    ts6 u  z% G) o: v, U' M7 D
    Out[6]: Timestamp('2020-01-01 08:10:30')
    1 Y7 F$ n. S5 K- ]5 i# Z) d1) h6 ?' V: V, P) `8 d( V  o; D
    2
    4 U6 E+ p/ u; a6 t3
    8 {, ~- k) g2 `+ ~; n' j3 F' [6 K4
    7 Q0 y3 E# h4 C5! n, i0 V- s1 M+ p
    6
    ( a' z# M3 e" \; o- F* G! c7: B5 N5 z: Y5 d( v9 f/ Z
    8( N7 h' I9 P. [3 L! N' U8 D
    9
    6 c; x/ P; b7 s& o通过year, month, day, hour, min, second可以获取具体的数值:& [- F' ~& }6 K! Y$ A) J

    : g8 Z* o# |' v: ~8 o* Its.year
    ; t; k% M- j! F# hOut[7]: 2020
    ( C8 W7 c* e& X6 R+ Q$ s+ J' \3 O8 B. n( W5 W9 z1 S, ^2 N
    ts.month- G* H6 n) V: ]% b0 P! W3 f
    Out[8]: 1, r$ I6 h8 P/ |. M6 m! X

    / Y: b2 b; ]6 ^; o) u- M0 t% ]ts.day
    5 v# `7 X$ o- R6 }Out[9]: 1
    9 ]6 R9 R% J5 W( u' q1 F' o( ]5 D2 ]( Q5 w( g6 Q  c
    ts.hour! z; |, _: g1 j1 Y% ~$ V/ W1 |
    Out[10]: 8
    $ i  `3 t$ ^+ x% y* B- q# {: r, P+ S" T3 _- n' V! @7 A' D( @* b
    ts.minute
    8 I5 ^5 G$ F% t0 w( |Out[11]: 10
    . }+ K; a! W1 ]2 T; V" h: w7 F& Q! |3 ~1 K1 Z( a* w. ?9 f
    ts.second. F) R( u; p+ _8 [* p
    Out[12]: 30) F, b9 R( r2 c- ]# e3 X, p' z

    / w! \8 o. A+ E+ U" K1
      a( ?- H& f- e4 r2
    , P5 x! N! k) G" U; n4 A3% }& d& ~; Q( J5 B2 i  U. K
    4. J9 @9 ^! n6 Y/ q. @* m
    57 v- c: ^- m9 X/ |4 M1 o5 X
    6. _5 k3 M- [) U+ J# e
    7
    : b5 o; E* E1 o; J6 {- }6 N8
    6 k1 y3 K8 Z; C; Z' ~9
    ( i4 j; _  W5 F2 i8 T( J2 W10
    & m* B# J. F0 L6 G! _8 E112 U! T# S# W: Q. u
    124 @2 h% C0 h, {" p
    131 H+ E& B% V; `5 T5 d. P1 e
    147 f9 N) f- j" t3 W* s' v6 U+ v
    15
    ( A& w: b0 F' y" n16
    - ?+ B) z: Q% B% E6 Y6 ~7 n) \( x17
    % e9 I( A8 Z( h/ A' i7 V& K+ w# 获取当前时间5 x, |$ Z6 J" g7 I9 s
    now=pd.Timestamp.now()0 {' ^. I$ d: W* e0 N7 F
    1
    7 n6 H: k/ I7 x  p, m! N0 T, ~7 u2, O- w0 e: b0 \9 T) l& O
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    ' r# A' ]3 H! b/ ]T i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)
    4 T& d- {  z9 w; \- D4 LTimeRange=
    + I5 b; m- u% P1 p$ R  D# Q& \10
    ! n8 o- e1 R: q4 d94 ^* t4 i- U- B( s! K! S& j
    ×60×60×24×365" D; T1 S  W! I9 u
    2
    . U" q. X* k: z; w  I64" q1 A# e: x4 Z$ ~

    ! G9 b( D" h* f- Q* }, _) l' K( |6 z, L+ J
    ≈585(Years)
    . q0 Y% o3 M: \
    " ^/ [* m, K9 T: p通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    ! |$ T3 T" E. G: ?1 \) M9 W) u% ~, B% ]. f0 c3 m% x- ?
    pd.Timestamp.max
    * [$ Q! S/ r3 K* p2 X$ VOut[13]: Timestamp('2262-04-11 23:47:16.854775807')
    % v' y% d/ G) P9 o$ p, `
    8 r4 [' S% \* I3 E& q$ u4 z+ Upd.Timestamp.min& m' f8 g0 ]% y; s
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')
    2 _% E8 {4 b! l9 \4 [/ s
    8 S2 e6 Q$ ~- Qpd.Timestamp.max.year - pd.Timestamp.min.year
    ; T- H! u- U* T4 h7 pOut[15]: 585
    ( z3 B' q+ N- i2 I0 U2 l1& g- Q$ g! `$ Z4 w" r
    2
    % a1 r. X# u' ^$ X3 k& A33 C1 I  N- z7 g, [% B, J
    4/ b. A4 A& f5 p$ F" M3 Z4 P
    5( M7 ~5 F0 Y- x
    6  K% w0 j% V8 d# C
    7" ?) J, Z% t3 M  A" V' A' w" F
    8
    6 h) Y& [: w. y$ r1 K10.2.2 Datetime序列的生成
    ! Q% Y& w) t& L& D( npandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,0 N& q* Q7 i, b3 |  v0 B; Y
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    + j8 q8 c( }4 [5 N7 q1
    . J* _& b& f3 L$ J" P' h2, Q! X% q! _/ l" N9 r  ^* W. K0 ?
    pandas.to_datetime将arg转换为日期时间。+ m7 Z! n; e* }( \

    6 t, M. F* D) \0 Barg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。( x& H2 F) T# [+ M5 o
    errors:
    # o4 B+ Q1 k0 e1 D' k% A6 [: |% e- ‘raise’:默认值,无效解析将引发异常6 F1 o* ?' Q) W& X/ n  @' C
    - ‘raise’:无效解析将返回输入
    3 d6 F+ K9 U# F# m- ‘coerce’:无效解析将被设置为NaT
    % ^- O: z/ u" M+ S0 bdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    " n* ^+ _+ v' I% v3 i5 c3 Q! Jyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)6 e% M8 }8 H3 g
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档5 R% J) [  ]. Q2 H. j. {
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。, d* A) x% g( T! l
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    , A$ }2 L% E( G# u' k, H4 cto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:/ y  [6 Z9 H7 g* g) _
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])4 [  e  `0 R5 B( j5 V; n' S2 j
    & g8 u! k- R" @0 M& b. Y
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)" G5 k" p1 q, }1 R  o
    1- L' b9 a0 |8 P5 Q
    20 P9 G) F, a4 F" J( w
    3: j7 h9 p0 E1 F3 e  I0 k( ^
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    9 P2 X8 h! s0 D0 T) ^9 r4 z3 V6 }  Q+ v" c
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    1 n! r0 P8 H1 M( G' p- Z7 A& [temp( s$ v) @+ ]/ s: {; G+ T/ l3 h* M. r
    % |5 o* ]# P; N
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    ; ^9 J# V' A) D" g( _$ ^/ y1) T5 O5 j* r, g; ]& u6 ~
    2
    5 l6 B' f' I* u+ o7 `4 O! l% d* Q! q3* {) e: K- e2 B2 K5 j  z8 V) H6 m
    4
    7 T1 D# I+ a- e# f* }. c& p  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    4 f* M# ?! {5 U" M  p+ n, H0 l
    pd.Series(temp).head()" X7 v2 v# E/ E( m7 V
      M/ N  h6 O' K, P' N6 g
    0   2020-01-012 A2 C) V! T: e7 X" u
    1   2020-01-03
      w4 I# f$ P2 u1 n3 w( l3 odtype: datetime64[ns]
    ! U: |; h4 Z6 J0 n1
    0 A* N3 d" l4 ]7 m2 e2$ t& A9 ?) M8 L5 f
    3, T( [! E: D% R& V- n
    4
    ; V2 C  C, y; L6 {2 w4 V  b6 V5. c1 W6 m( v: q. L
    下面的序列本身就是Series,所以不需要再转化。
    - k4 H! k$ g1 j1 Z5 ]
    ' N0 ^; x) x9 G! d8 B8 H# wdf = pd.read_csv('../data/learn_pandas.csv'), [: N* Z  J( N0 `" E
    s = pd.to_datetime(df.Test_Date)
    - p! g: F% `" ~s.head()) b4 f8 Z4 w" p6 T% Q* a2 K
    " b1 w' x, b! w7 a! O
    0   2019-10-05
    ( f8 O: j' a  y0 }% X1   2019-09-04
    $ y7 W2 @% P1 m2   2019-09-12
    0 i! [) N1 G3 H6 u+ c+ b6 o6 p3   2020-01-03
    ) `  k5 g; }% X: g! J4   2019-11-06  Y) ~0 s+ n+ ^5 y. ~
    Name: Test_Date, dtype: datetime64[ns]
    6 T6 p- J3 D7 V0 n" w, C1: y/ I) x. t# K" Q
    2. D* F7 O6 j& u" a) `/ ~1 t
    36 o  h8 U+ d/ |1 N
    4
    , Y. Y! q7 U. l+ h6 {1 m$ M5! ?( Q8 L# @" A; I
    6
    8 t: x2 d4 ^7 X3 k" e7
    0 G1 w8 a, X! l* D8
      l4 |3 ?# U6 p9
      U% W: q9 ?( n& D' ^& \, T106 g7 t) y" p/ `. h% V* A' E. D
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
    " }( D* |0 O" u, N2 wdf_date_cols = pd.DataFrame({'year': [2020, 2020],
    ; g  ^% ^! q+ t  e# K1 R" e: x                             'month': [1, 1],
    5 K7 i, O: a' @# `/ A6 r3 W6 u0 @( f" P                             'day': [1, 2],2 z2 k, r" Q1 G; f8 ], d# R
                                 'hour': [10, 20],( N8 w% v9 u7 N; n' L; ?
                                 'minute': [30, 50],
    ; F& r% K1 }/ x. C. s8 p# w4 F                             'second': [20, 40]})8 g8 }/ b1 Y0 }  y
    pd.to_datetime(df_date_cols)
    - I  C8 J. k0 C8 v# T
    3 V! K" O$ J, g7 ?0   2020-01-01 10:30:20& P* N3 \# e5 x
    1   2020-01-02 20:50:40/ @; y1 g7 {5 S7 B5 C& I* D
    dtype: datetime64[ns]
    ! a3 k/ `- _) {7 z4 \- G5 P( n& L10 j, B) e- x' N- O9 Y: M/ e# c
    2" E  L/ ?& Y7 [. `/ T* s6 v7 [
    30 f- `/ E7 s+ e+ q+ o
    40 O% S4 f8 ~; H" q' f4 [
    5
    ) E- B# k0 |, r6 U- M. T1 H0 Y! R6
    0 f4 d) Z+ C* Y& D) F: |7# w" m/ }- p4 T
    8
    % T0 e. \9 F& _9: F1 t) b( ]! e: s$ x# @' [; u
    10
    3 x# @; K$ F* Y3 V9 V) ^11- P  {( W6 G2 L
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    3 e2 u) V7 y7 X. j3 z3 F  opd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    ) O! s# O; f3 ~% P9 j! lOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')) o% B" v7 A2 o1 {. }0 [5 ~

    3 f5 x; r5 H: c! Fpd.date_range('2020-1-1','2020-2-28', freq='10D')
    ( G$ r; D" }! {% L  {Out[26]: ; \: G5 H) ]4 n0 r- h  \
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',$ l4 a  E* n! c6 v) B- v5 k
                   '2020-02-10', '2020-02-20'],
    / b/ Y  X. x3 f* x+ l& c, @              dtype='datetime64[ns]', freq='10D')7 V3 w& K" ?( X& F# h( V; m

    % `7 R! U- l, x! T2 x5 H5 Npd.date_range('2020-1-1',
    ' ]5 Q! B+ p, {0 C! {              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天0 \  o# d1 F0 I0 @8 ^% P' D
      y* o+ x& c/ j' _4 Y
    Out[27]: . L' C1 E& [9 |: Y# ^
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',/ i4 w& U3 R7 n* L: S
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',3 }! @3 l' ]! I1 Y
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],5 w9 b; |4 ?; L9 L# {
                  dtype='datetime64[ns]', freq=None)7 z! U* w2 K7 D& h& r( l

    : _/ g" `( x/ ^- ?1+ W8 [. _( J- ]2 Z, k) U- k% D: |
    2& ^" w5 A/ Q/ v# a4 V/ Q
    39 U2 {& D7 Y, N# M, s) u% K
    44 k( s1 H2 d7 |/ Y" `, `
    5$ {" C2 q3 T% I; c# s( c' p
    6+ e) J0 R, K' k8 ~( P
    7
    9 G3 _% y; J3 c; z8
    ) M; }# P# }+ R! M* w6 O9
    + q; ~, b$ o+ h& M4 t+ U. p) Q2 E10
    ) t) W: G1 D4 X0 t/ J2 u11& O- H+ F) g6 e+ [: R
    12
    9 ?+ M8 J8 D: f: m13
    ! F3 z: n" E8 h5 t; S$ L) r' l14
    ! U, T2 F/ A1 q5 N& H  U1 x+ b, W( o15" D% M2 |- Q( o; ~9 M
    16  K; L) t6 v8 n5 Y# R' O8 h+ Q
    17% C- Q$ ?$ E- p$ y& Z" R) M0 `
    这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。/ c5 d/ p$ P+ n

    + F3 O3 r/ B1 V3 M( ]7 H【练一练】
    ) Z  A& o4 V9 n5 C" x# ?Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。% U2 H& C' k/ W1 ~9 t, }$ i
    , x7 F' n( c( N' W; v
    ls=['2020-01-01','2020-02-20']! ]9 H- j  `) I( ^5 D9 k; T% R
    def dates(ls,n):
    7 K6 b2 A: G4 ^& i. l    min=pd.Timestamp(ls[0]).value/10**9
    4 V, M) z( l; n- e, p) S' A. m3 ^    max=pd.Timestamp(ls[1]).value/10**9
    6 f* {; S! p  `6 `5 P3 C    times=np.random.randint(min,max+1,n)
    5 x& f9 d8 n( }, k+ `4 y# A4 u: E. F    return  pd.to_datetime(times,unit='s'); r4 M$ d# S% f+ ?
    dates(ls,10)   `+ c5 L1 l$ j  f# J) R

    1 X! x1 s0 r. pDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',, D$ w: x7 g, n9 ~/ D- J
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',5 q1 z. z0 N. ?+ F5 m
                   '2020-02-15 00:18:33', '2020-02-11 02:18:07',$ N" y* b9 s  K! u, c
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',3 \% S4 I  j: u; u6 [' v
                   '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    / i  [6 U6 l' w9 @              dtype='datetime64[ns]', freq=None)( E. b! Z$ V9 }0 ]" f) x" r
    1
    : @$ g% O; t" I1 V  b) V9 `5 C2
    " K3 w% q( \+ C& b1 F39 N" p+ q$ \# {+ A/ A" Q2 Z( R
    4% m2 `" W/ Y; G) N# h, k* m9 @
    5
    $ r6 E- q& S' C) M* Z% H0 ?' |6
    8 I  u$ ?" |9 S+ y: k: r77 D% u7 A, M! ?- _* O  {8 [
    8
    + g+ _& o0 N6 C$ E. a' t95 u) ^0 l- i$ L4 G3 c
    106 e/ J5 |; [( k+ e
    110 N9 A* K$ N0 L6 j" e; e* |
    12
    + y7 @% p9 s. a8 W+ d/ S" @13
    8 Y2 }+ d+ [8 A$ ^14
    6 J: V! f4 L( o2 Yasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
    3 K$ a& ^$ R2 C3 G& ss = pd.Series(np.random.rand(5),
    ( f6 \4 V* K+ V  `3 ^) r7 ?# K5 j            index=pd.to_datetime([
    : t' H$ B- E- f8 o, c6 r                '2020-1-%d'%i for i in range(1,10,2)]))
    ! p# \' J: c0 [) y: O; @" n
      n; z% c# t. F: u2 J5 J- a8 B1 t
    ( m6 o) N8 T  U- X3 us.head()
    # h" W, D  }/ |Out[29]: + {* U1 Y6 c) ~/ u# N' F! ^1 m
    2020-01-01    0.836578/ ~/ M) e1 q2 S
    2020-01-03    0.678419
    " h, y" N9 p/ C9 W7 e2020-01-05    0.711897
    . [4 T% w. @2 t, x2020-01-07    0.487429
    ( k  N5 }2 _1 }2020-01-09    0.604705
    : r7 D4 i& E$ e( h2 r5 P! q/ W. Sdtype: float64
    " H, i! H2 \( D2 z+ U* ]9 s/ V
    $ ~! B. m9 Y0 K" q$ E! h6 p2 h) ]s.asfreq('D').head()
    . M( u4 O# ~: L6 |  I8 MOut[30]:
    " M! {% t+ X5 c. A0 F4 c  ?9 y2020-01-01    0.8365786 `2 {3 V% T8 \: a9 x% l
    2020-01-02         NaN
    ( M; v% _, v% K: F7 g7 h, {2020-01-03    0.678419
    6 ~& p. [+ T4 ~0 i0 N2020-01-04         NaN: m7 \, h  F. P4 F
    2020-01-05    0.7118976 `! t! z! @% |/ h+ }
    Freq: D, dtype: float64
    5 g% a7 L) W: ?; I3 A! e& ]6 {6 w1 b; W! z; D4 a' ^5 w9 o2 y$ n& G
    s.asfreq('12H').head()
    2 V  h) |8 ^" \0 eOut[31]: 1 l$ O% F( z( Y. g# y4 x# Q' J
    2020-01-01 00:00:00    0.836578
    ( s; u2 Z* ?2 }" `8 a2020-01-01 12:00:00         NaN% f  x7 }2 {7 N8 _" p  U2 k
    2020-01-02 00:00:00         NaN
    + H: j5 n! J5 h2020-01-02 12:00:00         NaN
      }; G0 q9 y! a7 ?: _2020-01-03 00:00:00    0.6784196 m  x( r6 l( n2 {
    Freq: 12H, dtype: float64+ R" O! Y3 h( ~
    & @/ C* x7 J# |7 y7 D1 @
    1% F* Q/ b8 L* g) N, I
    2( p: R- q3 U7 E- [$ r
    32 F( \" s8 n6 z' E, z
    4
    4 S8 N5 W! {% |$ B6 `5
    % L: G. W( c6 v$ E6) d" V" Y2 \' D! a2 I
    78 v; I( M+ d+ t
    8
    4 L& H  l3 x2 v+ W: J9& _% ]  o$ I. i* G' Z
    100 H2 f. E; A( b
    113 g4 {% W# M$ Y  G' t9 ~
    123 f3 o% D) O) M3 r: ^
    13* H6 v, h0 o0 O, B
    14. K6 g9 n6 \& }" i6 ?5 X
    15
    4 ?( T9 t, r7 e1 I1 P2 }168 \8 r) s0 P0 q( I  M
    17, Q: K+ J1 R5 l) E! u  q
    18
    ; p% @* U5 `# f4 q8 u19- {5 `9 |/ r7 l
    20
    ! K' g# I7 }# c+ v2 r21/ I) T) y* Z) n: P0 j
    22) X' ]) z& ]/ ^8 C0 z
    232 }5 I6 C* h2 G' |( A  m, u
    240 Z+ I/ i( k- \4 G
    258 @2 g) W4 G) w* v6 ~
    26
    . M/ u- j6 c! M$ L27
    ) g( j2 E& J, U9 }6 Y" B* q+ Y' E288 X4 h. N0 f2 l: L
    29- x! K0 R# c; k9 [# A, H8 Q
    30) S5 N4 l/ e, F( C4 g
    312 F2 c" g8 n+ O9 H
    【NOTE】datetime64[ns] 序列的极值与均值) m6 u) z" c% O& N2 d) C) M
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。4 j$ |5 H1 M2 a' k  u3 Y
    1 A- U+ ?* U* C+ r/ g! \5 N1 V$ F
    10.2.3 dt对象5 G* ]# [% i) r, `0 C! h+ Q
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。8 k- [! \( ?; F. C
    2 |. ]3 `4 g1 M0 s7 [' [/ J/ |- n
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
    / i" T/ `4 ^6 L/ [& h, Js = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    * W& b, c$ ]7 b+ ]( e; i
    ( O/ ~8 K0 J1 [" }s.dt.date4 \6 D8 I- P( p  B3 v! z8 m
    Out[33]:
    7 O0 l/ y. }+ V; q0    2020-01-01: o3 J5 Y; R0 _9 m7 [' R% O; f
    1    2020-01-02
    ' j+ J/ L# m/ m6 o  c7 l2    2020-01-033 {/ @1 X: X1 z. J
    dtype: object
    - b$ p6 f0 y$ Z! ^/ L/ H  R1 q" [) ?7 s& R9 m6 U4 E
    s.dt.time
    + p& G8 K3 I2 P: LOut[34]: 4 `* G  {; y) }! s( b) Y+ s
    0    00:00:00) Z5 j- r7 ?$ [: Q* \2 K% ~& I( Y
    1    00:00:00( G6 x9 F# S. i# t6 P
    2    00:00:008 Q4 R) {& K1 z8 a
    dtype: object& N9 m$ \# M1 x/ P1 E

    4 |1 V% C) [! J; P% Qs.dt.day
    1 O6 t8 p% x6 iOut[35]:
    / N  K9 j. w) i! p0    1' Z3 l" ~3 \. O: N1 a9 e* D/ a
    1    23 k  ~5 j3 s5 t  D( ]
    2    3" d( |/ U$ L$ M+ c( U9 U
    dtype: int646 F% V# H; c1 H: j5 o% g
    0 I6 j+ \7 i% d9 l& O
    s.dt.daysinmonth1 I2 ]% E7 e/ b& F" I& D. }
    Out[36]: 7 I9 X3 ?9 e% R1 C6 F. P
    0    31
    ) d& @1 o6 L  I1 ~9 C8 X0 ?1    31
    1 A9 U7 {+ D/ s2    31* T' _7 f7 i; _  \" q2 p# z$ X
    dtype: int64
    ' F0 r* N5 @$ O
    * }. h4 k1 Q4 X$ w3 r/ ^' M5 H1+ J; _2 e3 K+ X0 |9 m' v
    2/ g6 y, |. u5 W# X
    3
    ' b$ d& k1 o3 L7 l7 N* @4% \0 Q5 G3 H8 Q1 ~
    5
    2 [: L5 S1 G5 k7 o% V6 S6' Q: V- h2 X7 N; h
    7  A& s/ t, ]% d( {, ?( D! i$ ^/ l
    8
    / \  C& h) ]/ d+ ~; {) {* l9
    ; Y1 \( E* p) u10! \8 a6 z) A; M9 s2 g
    11/ i1 }! c- k, b3 A' r, I
    12
    3 k1 p- A) L8 y, K5 f  X( k2 p13
    2 b" @7 M( O/ F8 |' A14
    ( A5 Z% X; k2 p) V( N) w/ Y151 Q9 b: L! C* I. b
    160 q6 E; o3 z4 Z, L& Z1 i; Z# r
    17- O7 M7 S: q, X/ P; u
    18
    * I% l/ V. Y3 w0 Z3 `191 L, a; t2 H" c7 Z3 M+ n
    20) M1 e: r  t, H8 p- K
    21
    - A: H% G3 W1 P: l22- c; V' [8 e2 {' A" ?. A
    235 D2 {" w6 ~  H$ O% }- e* ?
    24
    * K8 z) r! ?1 G3 |3 f25) E3 @  }( A5 M1 j# u4 X' X
    26( {/ I  l( G2 b# l& v
    27
    6 P; ]: u: D" S6 j+ |( C' L28
    / i; C7 e% J# n) U2 {29& r$ T3 h* Y& \
      在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
    7 D: w- ~  J% T3 ?7 a
    ! W/ N3 h* k0 H5 u' J. ^s.dt.dayofweek; C2 t* p# x4 ^# Q/ R/ `
    Out[37]:
    ( K' `- N; \1 l  O' F0    2' w. N; e1 I5 `% t+ d( Q
    1    3
    9 U, {1 z  h6 q0 H5 q8 h! s2    4, Y+ _! W# {6 @) E) c) G* E) F! X
    dtype: int64
    & {7 y  L( y2 A6 |
    ! x0 @$ J5 w- @- q& hs.dt.month_name()3 z* _+ ^8 g/ u3 g5 n% V
    Out[38]: . J2 _' c& O% w
    0    January
    4 g: P) X, N8 c1    January
    + c8 a- c$ R3 I% U2 V- [+ V2    January6 a* C  {1 |# v3 U6 m! ?
    dtype: object5 H$ O+ v% B# o: [! G

    ' D$ C! J% R7 V) b9 K, N1 D" `% {/ _s.dt.day_name()
    1 V% D* U  ?$ ~5 ?, D) y3 l) R9 |Out[39]: ) R1 M9 z( g/ {3 j8 [" j  n
    0    Wednesday. \# @6 V- h! P0 R+ w0 M
    1     Thursday
    6 i( _# a: B1 V, I1 m2       Friday
    , K+ X8 I  l& T% k8 u; U. Kdtype: object
    - }. g7 W' A5 Z! @  X4 W+ e+ J7 Q& S. H& m
    1
    % g1 K2 O/ E7 `# |* a" U% j7 \2+ D0 t5 C! f  V6 A
    3% u8 Y: N& d* B8 U/ `8 Y
    4& d; P- e. q/ k( \; q, D# T+ T
    5# d& J6 X, m& b) s" h  h* V
    6- G0 l4 I# Z: U! x
    7. I5 a/ F$ c, a
    8. J  ~- p- J0 `- f8 r( A, @' n; ]& z$ f
    9! I& t' F4 M% G% j6 f
    10
    0 U; _/ N0 J8 N1 s4 Z4 Q  F: O+ B116 J6 @+ w* j5 j: z  W# E
    12
    4 K/ k8 T, C% k6 K) u; N$ [13, ]8 ?, J5 T5 q# l9 V4 F' ?2 N+ i2 ~
    14
    / P9 h# v. j5 E" _15
    $ Q8 l- g2 O# V: t7 ]: x0 ^168 C. [* F8 o$ L" R" j) r& Z
    17; F& \/ r: ?6 D; n& G2 G2 f# u( }/ J
    18
    * j' m$ ^4 r1 V- k1 Q' E$ ^19- E# R) }. a; }
    20
    1 [$ I& t8 H9 O& O) t2 ]* P第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:" D! x8 n1 a+ x$ G. W8 ?
    s.dt.is_year_start # 还可选 is_quarter/month_start5 K8 c( S/ c& T
    Out[40]:
    0 O2 h. S" t3 M/ y2 |0     True. C1 I7 y5 e* K+ p# R) v
    1    False9 D! ~7 l. ?& I3 l$ ^
    2    False# i: g- z, I3 c
    dtype: bool6 g5 A$ C: h( N. d
    / m1 I4 _% r- U+ \/ w) T& K) ]
    s.dt.is_year_end # 还可选 is_quarter/month_end
    : t# e9 |2 }& e) Y( ZOut[41]:
    5 G  p8 x9 z( A) p2 `. i1 d0    False0 ?. G8 [% m3 `' w3 f
    1    False' b. \' G! `) M0 Q1 ]1 [
    2    False6 {2 Z! b5 v  J+ h- b7 A9 e4 g2 A
    dtype: bool% X: S5 o' I+ Q9 `- f. J
    1) p- P, C, W) B( e+ t
    2
    $ x" i3 o( z5 y& p' L+ S3
    ' ?6 T  R5 [/ n# Q) t6 V4+ I, _3 V* }0 T
    5
    0 v# r$ ]. o# u  ?- ?/ u4 X; ?5 _67 P4 O2 n2 U1 S2 o( Q: y
    7
    , i% y% C) A' @, ^8) }7 `  r2 h) [: }( S) m
    9
    / f6 l' Q) B! A10' K  C2 m& C8 I4 P8 k
    11
    8 C4 }3 ?+ j- d1 Z8 V6 o/ X2 D125 N" L5 l5 ~. E9 S. G
    13
    6 D$ w) \( C/ S# G第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    0 i/ o5 [8 D9 G5 W' E5 t! Ls = pd.Series(pd.date_range('2020-1-1 20:35:00',- l8 t6 U' h; T& [" f* P' J$ Z3 u
                                '2020-1-1 22:35:00',  g6 V6 H/ z( J1 u+ t' _
                                freq='45min'))
    1 q' ]: Y5 i' X1 p
    0 S& ^% q' h* ?1 P
    1 O. c0 p! Q) P! fs9 ]$ q/ _2 V: u) ~; Q' y
    Out[43]:
    - Z5 ^1 t# B' {2 e0   2020-01-01 20:35:00
    " s3 ~# R: G+ U! _+ B1   2020-01-01 21:20:00# P+ C+ e- s0 Y; a# x6 q. q
    2   2020-01-01 22:05:00
    : d' c# Y- U0 H9 M# K2 pdtype: datetime64[ns]  v5 h6 X; X/ T$ d1 c

    4 a  H  w: r) x! ms.dt.round('1H')- H% E+ z. V; ~- }7 I9 F; |2 T
    Out[44]:
    7 i7 M+ x/ u7 U3 W' z! J( f1 k0   2020-01-01 21:00:000 D, \; r" _. G! Q0 c6 s: B& _9 q
    1   2020-01-01 21:00:00+ b  k, [4 Y* k- c% f" W' B
    2   2020-01-01 22:00:00( Y, `9 N8 e4 x. n- i1 [
    dtype: datetime64[ns]
    ( O2 ]7 i# y$ F5 I$ M4 U" E, z
    : |2 ~& G% ], u/ Us.dt.ceil('1H')& l- K+ r# M/ |) w. z
    Out[45]:
    + @* f3 N( _/ Q' ?4 v0   2020-01-01 21:00:00
    , }( |9 A! j' Q( G8 y; y& g1   2020-01-01 22:00:00
    8 ~; |5 F9 x3 I! v2   2020-01-01 23:00:00
    ! K' d0 U2 @5 S* K3 cdtype: datetime64[ns]
    8 g( s- l! O7 Q2 P- q- n- a8 ]  d" \; ~" E' w: B0 g
    s.dt.floor('1H')
    5 H! U& e+ c' J# ^Out[46]: : w( h( q# e) q+ G. r
    0   2020-01-01 20:00:00
    / u! ^6 ^; J: \& ~3 L1   2020-01-01 21:00:00
    : l8 U+ ]! V* @1 t* k2 ^8 P- P( C6 ~2   2020-01-01 22:00:00
    # V1 \+ b$ `. K- ^  \$ h" fdtype: datetime64[ns]
    ! B( B9 ?- O7 Y0 D' k7 d3 A6 b5 i5 b$ V. \! F3 _' k' j
    1
    ) u! t6 a" k, \; q1 a: W2
    9 Q* @! |  Z# `8 W: N3 R" @3( a; J  p9 Q1 ^3 {
    42 @2 r) P7 j2 e3 x1 R  P4 s
    5
    : P3 Y( d1 [3 q5 k: I% r) v6* }# l6 h6 g' d; d$ O/ d2 G$ |: F
    7
    8 H- Z! s% M- @4 j, i8
    " G9 N% g- V3 y/ o. b5 G% g93 D/ \5 b2 F! c+ b* j
    10
    5 T8 n3 Q" q- Y6 a& e117 E9 g# {) i6 X
    12
    4 D) S2 ^$ W, \( c8 L3 J' W13
    , N* Z( E7 g, l1 ?14
    : E* v0 Z7 m) n6 ]15
    2 f5 w% |9 c/ W, {16
    9 O% r4 s7 b; |: E17. e! Q" R8 }# g1 S8 @1 M
    18
    3 v. y( e! N6 y5 w19
    ' x. p+ j- ?9 W+ L( V20' C3 T4 W6 {" \
    21
    ( _3 ^) n. I+ z22, R; d* q. Y; o$ z5 x; T; Z& y
    23
    ) m" T+ U* W1 v/ h  T2 l" F5 _% Z; x- u24$ Z, h3 w7 ~; j3 R
    25
    ( M0 W, T4 W' Y, l: b) Y26
    - u8 z- @* d4 j7 K8 o+ p2 _8 C27
    5 k) v' H% Y9 m- ?8 }9 w28
    - g/ \1 r9 z5 u' ^# K9 D6 L29
    : h4 c$ ?3 [6 P& Q) }1 e30
    6 ]8 T) r. v6 f7 g' Z31
    3 q7 O9 {3 l( }9 _32" y( z4 ~& J1 E  z! p' e
    10.2.4 时间戳的切片与索引# B) c3 U  w4 F* o6 |$ Y) v- H0 s
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:7 s: k) N8 P" {$ S) X

    - t/ X8 A; W2 `. A" y4 o3 h& Y利用dt对象和布尔条件联合使用1 g; p3 p/ W* \% {, W) }
    利用切片,后者常用于连续时间戳。
    7 ]- p' G- L: t% e0 g  P1 h: Cs = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31')): _) [0 J5 k& b! a: M; @
    idx = pd.Series(s.index).dt/ r, ]0 A4 ^! n$ f
    s.head()4 {/ f2 Z3 c, g# F* D0 a
    ! @3 v2 t( x4 e; C- a7 F
    2020-01-01    0$ e# g" N1 f0 h* z
    2020-01-02    1
    : D/ v- M1 ^& Z* b! Z! q4 i2020-01-03    1, t: v8 M! S% d
    2020-01-04    0& [( L! j, `" |
    2020-01-05    0+ s% M. {( V1 i3 n$ S& C
    Freq: D, dtype: int32) u6 A8 Z+ p1 t7 V7 f  d
    1
    4 P% b2 e: x0 ?2
    7 x5 @' o5 ], A+ l/ }+ U- j8 P3
    9 X7 [" v, R- \/ Z  n( B. g; l+ W; {4" A' }& h3 @. P5 T( |
    5! O. }( T! l6 t" S" L7 F
    6& _, J" i" {& y# w0 E* r) V+ S
    7
    % _) B/ H$ h: v2 q5 X8 a; m: Y8
    : B! F! |$ ~, G6 W2 Z- z9( C# ?7 k* ~) m# X# G( g* C
    10& c" A& k) T) [8 |
    Example1:每月的第一天或者最后一天) K9 Y5 u) B* b/ n5 n9 i

    - I# c" F! W' L# y, u6 Qs[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values4 D9 S% |2 P9 ~& U
    Out[50]: 4 Y  ^% Y; s" U0 ^9 x! j' t
    2020-01-01    1
    2 }9 {, H/ w" s$ a3 t8 |2 W; i2020-01-31    02 T6 N5 H: ~3 u5 \
    2020-02-01    13 M& b% s% Y) J$ P" m8 Q
    2020-02-29    1
    6 l9 A8 W/ ^8 h6 }( b, C' T2020-03-01    0
    4 d1 s0 L5 J% `/ \- F) ^, ^dtype: int32
    ( E; `$ Y" b3 y% o1
    % |& y& O1 `( W! r* a) n2
    * G; z3 Q. t. J/ m" C4 f# Y38 |2 b% `8 G1 k% C; K9 O' Q
    4
    2 M3 H9 ^, |, A5
    1 }# e6 z: q# ~" \6# \8 g$ |  [- z! x
    7: `  A& p8 P1 k" ?. d/ N
    8
    0 X+ u7 C7 U2 Y4 o" @+ XExample2:双休日
    * l2 v+ V2 p7 Y. D+ n- Z- C/ O: `3 s
    s[idx.dayofweek.isin([5,6]).values].head()
    / r( m  H7 Q# I; |  [% YOut[51]: % M% g7 W3 N- ^2 y: N
    2020-01-04    16 }' E# }0 F$ n6 j/ y$ |% O3 x9 p
    2020-01-05    0( v! f$ N% o, Y2 x/ S  W& R
    2020-01-11    0( {1 q& O7 c* E# n( c& K) }
    2020-01-12    1% A* n7 e# h; ?- n
    2020-01-18    1
    & w7 m8 s- S. v3 n1 udtype: int32
    - g) X3 ~$ N1 S: a  \/ Q0 q1
    8 w" F. e: n* ^7 e* A( k9 N2
    : y# K# j3 a" T! E5 [3/ I+ r8 f( B: h! F( j+ O7 e
    4
    $ j; b! f" ?% o  h$ n5 L4 r5
    . z3 U; M* R+ L6
    1 `$ ^% \8 N3 `7
    9 O* r& j5 R7 @0 C) G8
    8 w% _" r( v4 m6 f( g1 VExample3:取出单日值
    " J2 h: d' t0 y/ {: ]3 S4 d' b2 @' M9 x' W# V
    s['2020-01-01']5 R$ \) \: Q; ~( _2 L
    Out[52]: 1
    5 Y/ ?1 N- Z$ U3 P; F! ~
    2 L! w2 D: i4 Q! W2 }% As['20200101'] # 自动转换标准格式
    7 C; R3 E0 N; n9 C; a: _Out[53]: 11 [( d5 d1 R1 J; _* S
    1# Z0 e" o2 d) v# f0 ^
    2
    2 A  J8 G/ z0 q* ~% E* T3
    7 u8 I0 T' L* T0 \0 g4
    ; x8 r$ b/ h; |" r53 U2 r0 ~5 d5 ]/ y' A/ L: B6 M
    Example4:取出七月; T! L7 R2 p  W) O- j* X4 z. e; d

    . f: }( B3 A! u; |s['2020-07'].head()! ?; ?! Q. W/ H% e
    Out[54]: 3 n# k5 H, v7 M1 Y( P7 U  c1 @
    2020-07-01    0# e# ]) A  u% i. D
    2020-07-02    1
    % I0 w9 T  }( ?4 |1 Z  b! |! B$ ^2020-07-03    0
    5 c8 l  p5 w) {: D" ?2020-07-04    09 o4 C) w3 Z- b! p8 A6 @1 p
    2020-07-05    09 R9 j- T* c7 ~( f/ x  @5 T9 X2 ~! y& P1 i
    Freq: D, dtype: int32' r2 i  v: o+ }( l. y
    1
    ( X$ Z  n- v! o4 b2
      N! L# Y0 D. R, O9 |' T8 i3
    % c' A" p% W6 \4, _& _6 _$ S/ {: p4 l2 ?7 ~2 U5 k
    5/ r# s" j& M* \* i; _
    6: o+ e# Y0 L& l& f8 B2 B& m
    7
    1 H. u1 A# Y* V* R0 ?% h) F3 g0 [8
    : q0 K. J% Z1 m$ d3 b5 }2 xExample5:取出5月初至7月15日
    5 W- _, r, f! E3 a) W+ J. ^9 o9 Q2 e7 F- ]
    s['2020-05':'2020-7-15'].head()5 [; b) p8 [; e2 i
    Out[55]:
    0 A( V* ^* Q4 ~" f  y1 e& t& o2020-05-01    0& j5 z, m1 j* v$ v, Q
    2020-05-02    1
    7 k% P' l' i; ]' h2020-05-03    0
    ; T5 r7 t4 F2 n! K2020-05-04    1
    4 @/ A% q: p1 U% M; X2020-05-05    1/ X8 }. r; T  w; v1 F  E
    Freq: D, dtype: int323 @9 s, C2 H. P& v& G+ R
    ! X$ p* K5 H' e# ^) w0 c6 R
    s['2020-05':'2020-7-15'].tail()1 T6 D3 r4 [6 v# O1 ^
    Out[56]:
    5 W7 v  \/ X) ]. }; r) m( z9 q0 J) W2020-07-11    08 I, E9 X6 v) F" p0 i% R. _. S
    2020-07-12    06 V5 v- v8 h0 E' w# O+ J
    2020-07-13    1
    4 R6 N9 ^1 h! u8 `: W) b3 i9 A5 S2020-07-14    0  A% ]% ]/ R4 x1 c- H& w
    2020-07-15    1: S! I( s( G/ L' W. }$ Z
    Freq: D, dtype: int322 M% Y7 @% V: ^( |% F

    : C% P/ I/ Z5 }) A' \# s1
    ( q" F  O4 ]/ i- L2* m" F1 r0 y9 h9 ]
    3
    ) q% N( D8 J/ t1 b4 w9 l4
    * d# \  }: `! h1 R. R, q+ J' T5 m5- g- k8 g+ w5 h8 F! Y$ H$ V9 \" x
    6! a0 I6 Z, J8 G* [4 ]
    7; g2 S8 |7 \# x
    8
    + V. y+ d% e7 K: F4 h9
    $ x4 X. h" M7 n# ~: j10# I& z9 R" ^9 D$ Y, s
    11
    . R5 [. _. x' O( w* E: N& R. d123 n+ {) y2 V; k( U
    13- Q8 u! `5 |- D$ n
    14. e  q' n  [: p) I
    15, Y( H# D( B4 N) G2 L6 r' q
    16
    . i$ M6 r* @+ \17
    7 z0 Z, Y- c0 \" ?9 x, D# p10.3 时间差4 {3 \2 i* @4 {8 d5 F0 v0 Z# _# K
    10.3.1 Timedelta的生成" r/ d0 N0 B- I/ w! f( |
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)
    * q' [2 J; `& C, d  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。, @6 s) w& ~  p% \" W) s% r
      可能的值有:
    : W* a2 e7 I7 K2 I! p( b7 T
    " L# C0 v" k# y: V0 c‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’! h0 V/ I2 H$ b
    ‘days’ or ‘day’
    & U3 }) f" ]' ]1 S6 b% P‘hours’, ‘hour’, ‘hr’, or ‘h’7 n+ O/ H- p1 K3 A( j5 l1 ~
    ‘minutes’, ‘minute’, ‘min’, or ‘m’
    $ q. d. I5 b1 g  F; K& e) h‘seconds’, ‘second’, or ‘sec’
    : K# l- y$ R5 ?5 C* B. z毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’/ ?* }1 Y7 ?! d/ s% Z4 Y$ v( V) H
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    ! V2 F4 h- S/ \8 L2 ^) g纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    # \+ X2 S; _. l4 i) A# ~时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:2 @: e8 v/ v/ p% o
    pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    ( Q; B; ]+ }' j& {; OOut[57]: Timedelta('1 days 00:25:00')* V4 p8 v8 ?2 i' N/ G- y0 O
    * A+ Z8 Y) c% _
    pd.Timedelta(days=1, minutes=25) # 需要注意加s  ]. Q+ d. t% ~2 a
    Out[58]: Timedelta('1 days 00:25:00')1 X5 V1 O. T9 M1 q5 D7 \; c! j
    & k; N- ?- K& t0 V! Q3 I
    pd.Timedelta('1 days 25 minutes') # 字符串生成, [1 ]4 \4 ?5 S, C
    Out[59]: Timedelta('1 days 00:25:00')
    & |! r5 \1 z1 x8 _# M- b$ Z: y* C# {3 J' s& x4 j
    pd.Timedelta(1, "d")/ s+ C( f& g6 e) N4 D, R- ~$ v
    Out[58]: Timedelta('1 days 00:00:00')
    4 c9 p! R4 n! a" X8 M* l1
    + V* H" {1 V9 S, z  V  D% Q6 G8 q4 y# {: w2
    3 F6 o5 T. e3 C  B* h33 h  P7 ~* ~2 ?; r* k2 E) `
    4
    % u, R# a1 D; |8 z5) M4 P# u2 Q( {& p/ l7 M
    6
    + V& [3 m- D# n. i* y, c/ P7* T1 \/ F0 \* E' T
    8) |5 h3 W2 v0 g% z
    9
    2 a  K! k' s# J10' q( T' s+ I- G# F6 W
    11# B( n% V# ?) k0 C
    生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :2 X. ]: a6 k0 c" ~- q% ~
    s = pd.to_timedelta(df.Time_Record)
    - ~+ c* K. E7 @/ K- Z8 M; I' G+ |9 d; l) C
    s.head(): G1 \; m% l5 v7 S/ d
    Out[61]:
    * v1 F& d% C2 y! k6 H! K0   0 days 00:04:34; w  j  d: o8 g9 @4 a: N( l
    1   0 days 00:04:202 ]7 H4 V  E0 V; T
    2   0 days 00:05:22
    1 Z5 M6 w4 V) V( ~0 N& ?( F3   0 days 00:04:08
    ( h. s& c1 ~4 s2 s7 o; P4   0 days 00:05:220 X, l8 U# F9 s" t
    Name: Time_Record, dtype: timedelta64[ns]
    ' h+ }/ n4 P! B$ F+ E13 k6 ?% T, F/ s9 T
    2
    / Q2 s* i' {6 e30 ?# n8 w- _- e' \% x, i
    4
    % b2 {  @5 d7 Y8 k0 c7 I: t5, R; S9 c6 q4 O
    6
    , r6 T9 u7 ]5 w% W$ i0 `' S7
    5 i3 N: p" b; v9 g' C' y& r8
    8 [( I% F- m( F% u$ B* q9( y7 }$ {5 l7 x3 y" z5 o
    10
    / u  ^0 {% j$ F与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    . G1 t" Z2 W# r) {2 K( [pd.timedelta_range('0s', '1000s', freq='6min'). @8 ^3 Z# y% S2 @: O2 b
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    ( ^2 ~& }& q+ @4 X4 e& g; f3 g
    : F. d. {! N5 P8 y8 cpd.timedelta_range('0s', '1000s', periods=3)
    0 O" ?' u& g4 r) K# f, k* HOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None): a! X! }9 o1 a. _5 s" M
    1
    ( h9 n1 N& A8 M8 q( F4 g2
    2 I4 }3 b8 N6 P& M3
    . w6 _. O( a5 X) O; R4+ H& b2 ^6 T8 I3 n) J& _: [
    56 B! [* B  E: F% n/ W2 c
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    : F6 m2 [1 R5 Q. x; js.dt.seconds.head()/ a* W0 i- q1 \$ X/ J
    Out[64]:
    5 z# J7 k, |$ Z  a- p: {  V0    274! K/ ?1 O8 |& ~. v% q: }8 {
    1    260
    % `9 Q5 u, s" m& X& C: t2    322
    ' w1 H) {; G9 B7 [+ d" ^! g, f3    2483 o/ |, R6 N, |2 J% Z
    4    322
    * i* c5 p7 r$ v4 h" FName: Time_Record, dtype: int64
    * R0 Z0 w. T7 N0 q/ ^2 f6 `1) ?! t  ~- F) r! t( ]# d; B
    23 P- p9 z7 |+ T
    3# J) n2 H* W0 d- J3 T
    42 j& g- I& N. C2 L# Z
    5. r/ b. a' T; n8 l0 A* D1 f" }/ j3 B
    6
    ) D  n& T! R# y: Y7# Z* k9 h% x, {+ d5 E4 N% r5 A# Z
    8: n3 M" E( [! @1 u  ?2 q( d
    如果不想对天数取余而直接对应秒数,可以使用total_seconds
    $ q: }" e  ]5 w/ C6 G. O. l' G1 ^" O" S
    s.dt.total_seconds().head()0 V  N2 g8 r. d* s; f
    Out[65]: 8 {# d6 X" D8 T6 Y! g
    0    274.0/ X# u6 ?& T, |/ j
    1    260.0
    ! ?+ U9 J5 b  h4 _; m3 D: H' s3 p2    322.0, D  p5 k4 O% D+ h/ M' n& V
    3    248.0" F/ `$ U5 n3 u1 ^. }9 |0 C/ t: g
    4    322.0' b8 [: y/ R6 P8 n$ ?5 }) Z
    Name: Time_Record, dtype: float64
    + Y! o4 L$ O' e  G, l$ ~0 C8 ?9 ~; u1
    3 P& c; }2 r; w( D& I8 p4 Q* O2
    ) x: N0 W5 b, C; x3 g: s2 ]3
      w5 N- c( I0 v7 j4 R, m' b4. ?: W7 M. @1 P0 ^- R5 y/ U
    5
    $ A- z% T9 w* L+ v, ]$ x6 o' R6
    ' E! s; g6 D, b' E6 y7
    # P+ e$ D2 \: n7 h( B8
    5 o9 \' G0 k6 X* m7 @1 T- _; n与时间戳序列类似,取整函数也是可以在dt对象上使用的:' ~- s, P9 f" R2 x# p# d8 U

    9 n% O# j% S9 A: [! Q. ypd.to_timedelta(df.Time_Record).dt.round('min').head()
    & N# H2 u. A# }8 x( x1 ~Out[66]:
    6 |% N/ m, N) w) U3 ]0   0 days 00:05:00  B$ d, n2 K7 ~) m  J
    1   0 days 00:04:00% Z. L( d: J( t3 [
    2   0 days 00:05:00
    4 M; s8 b. c" Z! Q% N  w3   0 days 00:04:004 x# f! F* Q2 f0 y' i5 f& G
    4   0 days 00:05:00
    $ P+ l. U% o; sName: Time_Record, dtype: timedelta64[ns]
    7 S  r# o/ x! r7 @9 O# C" J0 I" x1
    # z* [5 x( g1 `) i7 x7 W/ J! d2
    ) {9 E, W$ T8 Q  x2 c, h+ C- \3
    ( c  }+ Y3 W4 B% U* C2 u2 u4
    + G9 A) B' T9 m: K4 U  W0 u5
    : W# V& B$ U' _$ ~; O, N6
    $ |( J' Q. D* b) S$ x7( V# Q: W$ ]: C" Y) K& `
    8, |$ h- w, K' G3 D, U6 s6 J9 E
    10.2.2 Timedelta的运算  B7 _: N# q- f& O
    单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:0 u& X" w* L0 S" P3 }8 @8 S) [7 c" z
    td1 = pd.Timedelta(days=1)4 t2 w( d. o. [$ ?7 d  ~4 K3 y
    td2 = pd.Timedelta(days=3)
    1 L- u- o. M9 Q4 L5 lts = pd.Timestamp('20200101')
    + R' A" [' D3 c$ B* \% H) M
    3 y4 ~9 R: w# `# _" `td1 * 2
    ! ~) x! @' L8 P9 c) `9 p2 xOut[70]: Timedelta('2 days 00:00:00')
    + l1 R: G8 m# Z% D" b2 V$ p* [3 I8 ]
    td2 - td1
    : ~& u: L+ w4 c* U: F. bOut[71]: Timedelta('2 days 00:00:00')
    ( k2 P$ N; ]+ e9 H! B
    $ G. X; p3 ?# r* V0 Vts + td1
    ( o9 @9 n- }" W7 NOut[72]: Timestamp('2020-01-02 00:00:00')3 A% p/ ]$ m7 k6 K  i
    * w7 \& T; e" E4 c; G9 ^$ t
    ts - td1
    " H- A) Q4 s0 ^2 _, y, E- @+ }Out[73]: Timestamp('2019-12-31 00:00:00')
    : D0 u; v& }, I" g+ i& H$ H13 b1 T. H5 X3 h' J- z- {
    2: P8 w% L4 r* F5 S' V
    3  ^! o6 x" m. _8 b
    4
      s+ J4 }  v7 _8 S& ^50 n+ N7 K. P1 w4 R& x
    6" @$ I. m" u, U4 Z5 Z9 \% ~1 W
    76 r/ G: [; K3 V7 p! A
    8
    + R; O+ e6 k3 P# m9  v7 E/ _" _% C6 T: K* T
    10
    1 U- ]: ^( s% m; d& \11- ^9 E0 `( B( I4 t
    12
    / X$ Z, O0 g1 W* |: ?8 L13
    " H% ?0 Y( \4 i3 W; p, B14
    / Q- l& ^: q% I+ H15
    : ~; V! }! y% M: O' K7 d4 t% y时间差的序列的运算,和上面方法相同:9 K3 L. `! {/ z) s  I
    td1 = pd.timedelta_range(start='1 days', periods=5)0 m; R' y2 o+ U- `. {5 o$ p0 T
    td2 = pd.timedelta_range(start='12 hours',
    " c6 Q  t$ f/ M! _/ F) G                         freq='2H',( {4 s' L" W/ \- h5 h* `
                             periods=5)
    0 q0 f: W+ e* Y1 T5 [: Rts = pd.date_range('20200101', '20200105')/ z" C7 D+ y/ e  @, `+ V  c
    td1,td2,ts
    ' @! f2 `$ Z/ `: j9 {1 ]2 \5 m( ]% p: f
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    - n) [4 T6 S4 p6 v4 mTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    6 V. r% r9 A8 m4 Y8 D! a" x                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')0 h+ |0 ~8 V4 u8 |: |
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    $ A0 r( Y, Y2 J               '2020-01-05'],! y: B+ d0 A, h8 `- B- s/ t
                  dtype='datetime64[ns]', freq='D'); m9 _! I( R& |0 C
    16 k( y( f6 a& _1 O: E. F1 Y
    2
    : P# p3 a  [7 a& F3
    ( `" L' `; i) H3 z6 d4
    : x+ F% \% G' \% t% {6 Z5 {; ]5
    4 `1 ^: z2 V; x9 Q3 U& T' R67 u; D0 X. e7 G* Q3 w7 w' Y
    7
    9 [" o6 N4 Z' ]8& H( D0 Y, Q: r1 C" V! \
    9
    1 p' u. S' ?, Z3 q1 h( M10
    / x8 q( d( G& A9 t! L: u117 M4 F, X9 l/ {- I& }( }, p2 ], V3 ]
    12
    & x/ z& h# S  M; W( _13
    3 U6 Q( c/ }. N2 @$ ~& S+ X7 ttd1 * 5
    ! l1 }5 l- Y+ }" X& wOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    ! ^4 j# I/ O$ J1 U9 Q% D& O
    ! J. l4 b& g) G+ w% K+ v2 }* \# Z# Wtd1 * pd.Series(list(range(5))) # 逐个相乘
    7 L7 t& f. p) G( Z3 c- V  SOut[78]: + r( o  i: F2 x" k. `! F+ n, M. L
    0    0 days4 p5 J: X+ p5 R* a3 s- q/ O
    1    2 days6 U# d# e9 C1 I2 h" n3 B, V5 _; K
    2    6 days
    % V& n' ?# O9 [6 d: B' n$ k5 |3   12 days
    7 o$ V$ |" l" L$ Z; ~4   20 days
    4 f4 F4 S6 l6 b2 X. a2 k3 C* {  _dtype: timedelta64[ns]
    5 X, p1 p0 T5 I2 B0 V
    4 X* O( \2 N0 [; }7 _6 `; Ftd1 - td2* d0 G  M  M- |9 `) D+ X5 v
    Out[79]: , i' j  \" a" t# A* }% z, [
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    0 }6 @+ U: ^8 O" F& X                '3 days 06:00:00', '4 days 04:00:00'],, q( F5 p% J, r9 ~  w5 {
                   dtype='timedelta64[ns]', freq=None). G5 ?! O6 x* I" Q
    2 C, v, z1 _" P1 o+ D
    td1 + pd.Timestamp('20200101')
    ' }  W: |2 h' Z" \Out[80]: # Z9 ~6 L7 G$ [, U9 W
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    , x0 |) K6 O0 ]* @0 E               '2020-01-06'],dtype='datetime64[ns]', freq='D')% }% E1 G& B6 _0 Q/ u; P3 c. O
    0 [4 Z& K5 n3 B: u8 S1 ]
    td1 + ts # 逐个相加
    / X6 W) z, f% a! t$ l1 W' KOut[81]: 2 i9 w+ U( `9 r, L: @
    DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',% B# Z5 |% y. |! G3 a
                   '2020-01-10'],4 B: I; A( E: f6 K% W3 w+ g' n
                  dtype='datetime64[ns]', freq=None)# _- x/ D8 z9 S$ Q  I) G& q) Z! {1 p2 q
    $ _! S  ?* W; _3 M. F% C4 A
    1' r( v& I0 Q. y0 Z# L7 J
    2
    6 w6 V) z5 ]/ V  z/ Y3
    & a' d+ t  V0 X8 v+ _$ n4
    3 l9 L. d+ C' G( ]) C' b7 _5
    7 h* ~0 y& [" A8 W) D6# i5 N# r$ P6 B) H3 C9 ?  v
    7
    % G9 `: t7 r0 L7 F( V8 {  D- \8
    * F* E! U- u7 Y' a# ]% Q4 |9
    8 b7 O: [& Z4 l$ n9 t7 V10
    4 x; C* u3 d# V5 C' \7 U, U! W11
    2 u0 q9 G" d" t8 `12
    * ?4 [$ j, T3 J13
    # a/ `! w9 q+ y& W$ y- w. W4 `148 Z' E! ]) Y6 u" V) p8 A
    15
    5 j# E" m" @9 N, I9 y163 W, M' o" n6 x
    17
    $ h$ p8 g1 j3 q181 v- }& c: E0 s/ y$ l. l
    19
    4 w- w  o5 H4 ~$ H. d+ E7 n20
    & c, n; y+ D4 [/ O+ e. b21
    $ Z& P( _7 J8 Q! q2 T8 l( W22
    2 d& r% f1 N4 K8 |7 \% x8 W23
    : s( N  n# T- o; i3 w) D24( ~- q! ?- I/ B/ z0 W$ m
    253 [6 f# y1 T) w9 @& f6 b" r6 x3 H+ \
    26
    ' C- I, l% F8 X5 t# d& w2 q# T27
      b2 X! e4 V1 E289 \, d; w6 x5 }" X8 `
    10.4 日期偏置
    1 F. ~1 |" f- I. B10.4.1 Offset对象3 C# ]4 x" z' D2 p" V
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。$ O7 ^) X( r& K6 I! Y$ l7 x9 y: m

    8 r/ s* e1 \: [) s) `# iDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    % L2 \$ V: ~. L# l9 `3 {' \# ~. o: m# S5 y% o2 W$ e
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    # a* H" O7 \+ }' J1 Ls.kwds:{‘week’: 0, ‘weekday’: 0}
    # E$ f- d0 [5 n6 q" P6 z2 zs.wek/s.weekday:顾名思义- w; N! z4 e: |" l! q: ]
    有14个方法,包括:
    1 B7 p$ o; o2 y. n4 i: j( k
    . R4 o% l7 y. b# h( aDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    " I4 f* v8 H4 L- Opandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。3 J0 i4 e3 N( X2 P2 @

    ( r2 e4 Z* V4 f! [, V; @( z# J有两个参数:$ E+ L% A& `6 @7 p$ L
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
    2 p' L" H' \: n6 Q0 k+ hweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)* I) I9 `/ W- t& \) u
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    9 ^) _7 ]* T+ w- H, u: p% [8 i/ E+ M( }1 r' R: r
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    , K: K0 d( \5 ^! u' `Out[82]: Timestamp('2020-09-07 00:00:00')
    ; o  J* J  c% t* `; y; ~! L9 b  A0 L& i+ H7 O0 a6 j3 z& e
    pd.Timestamp('20200907') + pd.offsets.BDay(30)
    ; @2 e* X5 X4 [% p$ lOut[83]: Timestamp('2020-10-19 00:00:00')- I9 o- B) v; N' G
    17 d" O8 J( G4 U" H6 J; p
    2
    9 ?1 B% r8 ~% B9 g& f% E0 t; V7 e3: B3 k. X; \8 w8 x9 x
    4
    ; H& l  p  D2 ?& ~( e2 j9 U7 L: Y5
    3 c$ {  K+ ?" i6 \  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:; j6 E$ Q* e+ N6 p4 J0 N

    8 a2 J3 `3 i) k) u7 Ipd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0): `) v0 ]- |- N  u/ C# K
    Out[84]: Timestamp('2020-08-03 00:00:00')2 H* r. _" u1 w% E8 t& H; ^  ^$ D
    * T- Y8 T: ]1 h6 \& ~$ F4 a  e' h
    pd.Timestamp('20200907') - pd.offsets.BDay(30)3 z, p9 M( Q2 d8 X
    Out[85]: Timestamp('2020-07-27 00:00:00')
    8 Z  v- g7 k/ ~" P  h7 G- s& t  w3 C5 ~# X* I  M
    pd.Timestamp('20200907') + pd.offsets.MonthEnd()
    + q1 D$ O$ h. {8 \- p. EOut[86]: Timestamp('2020-09-30 00:00:00')
    6 u8 s) r+ j6 `1
    . g4 L9 |" _$ G% M: W( ^6 H, w28 ~6 @2 a- I9 R) k1 v) y
    3
    2 i8 j7 ~& u" x* J& J43 N1 x3 T' q3 S0 L, K3 w
    5" ~5 A0 T: b/ B8 Q$ d; f
    6
    # Y9 e6 d8 _' {% e7
    - G  n5 Z( E6 U6 w) W1 j8 M  v84 J3 ^. H0 H5 o* P' T' t% k# t
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    ! N: ~" J" V- Q- k  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:* q2 Z  [- Q; e% C5 k6 g
    ' B3 c/ X7 ]0 M
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    & s, [. }8 V# e, Q: P3 Hdr = pd.date_range('20200108', '20200111'): Y5 U6 M; O% P* ]& ?' H

    ( P6 w  M2 E- }4 P( S, N- Qdr.to_series().dt.dayofweek1 [# o) `) }# j8 k! Y0 R# c& g
    Out[89]: ; `8 N: w* `6 G6 K- j# C/ \- I
    2020-01-08    2
    5 y$ ^8 o* x. [  u+ ^: p2020-01-09    33 @# L0 a) d# |7 X! @! r
    2020-01-10    4$ D. \* K+ ?6 Z1 Y
    2020-01-11    52 ^2 T7 d4 G; U
    Freq: D, dtype: int644 B# o3 ~$ c. `) G& A
    5 ?! d7 f* h7 K4 x2 P
    [i + my_filter for i in dr]
      D( `! {, D7 T& ]  ZOut[90]:
    % D( C: |2 \- ?" U[Timestamp('2020-01-10 00:00:00'),
    $ G4 J' |; y* J' ` Timestamp('2020-01-10 00:00:00'),
    9 a, J* u+ l- ?# v" B- P Timestamp('2020-01-15 00:00:00'),
    ' f: @9 c; `  e$ F% U Timestamp('2020-01-15 00:00:00')]
    " s7 |& a" F8 y; c' S' ^# S7 w, s) v
    . d1 F" Z- G6 u4 o5 O; R# k+ m4 U1* W/ t3 J) T1 w& Z" c# u6 \
    2
    ; h/ Z% k4 s, w4 u& H3
    - Q/ T3 K0 J- ^4
    : I$ I- M4 M  Q+ w' O5
    $ @+ A$ U- @- V" ^. z' `6
    / `# T3 y- p8 ^' M' Y; L7
    + q  C; f' o) c# V, E- A3 N  W8
    3 e1 ~% {# K/ i1 v( A9 _' z* l" \9
    $ T3 T. D; w0 w. l10
    + ?% I; ?& ?) O11
    ; R. W& E* |; W12% X$ ?+ k6 W9 }; f
    138 l$ U- u& b" L  {- i7 ^
    148 t- W5 B' Q2 J! m
    15
    ; U7 K' k( W" A9 |* }  @2 i16/ U, u+ \% o2 q! o$ h) V& Y
    17
    " `5 b; U- g! Q: h* ^; ]  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。+ i  o1 N) i& y' K: Z8 X. ?$ C5 B0 S
    + t8 i, C4 ~' y
    【CAUTION】不要使用部分Offset, l8 t( [, \2 L* `
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
    * f( C0 E, t) ~5 @/ J( M2 U% k' V4 }* m! a' ?- M
    10.4.2 偏置字符串
    : G: y2 s7 u" V0 n1 x+ c  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
    7 _) s' K0 n! }! B1 o
    ; K4 e, T- j: e# @1 U( d& u, O  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。1 h* b& O- K7 o
    ; b0 u  w% E7 Y* E0 h
    pd.date_range('20200101','20200331', freq='MS') # 月初) e0 ~5 N( W; `3 c0 L9 Q1 X  M' x  D
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    % I2 @+ d6 ?! g5 O* ~* w: S. Y7 v! E+ H: `
    pd.date_range('20200101','20200331', freq='M') # 月末
    4 E& I" _$ ]( L) A5 `1 m8 WOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')6 C- _# N% W5 h' w" S: t* G
    : i, C' s" N, f
    pd.date_range('20200101','20200110', freq='B') # 工作日. R. `+ \+ p2 S7 v# }
    Out[93]:
    . Y* I$ s. I4 Q$ i/ w. XDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',4 |* X! S* ?! E# C8 v
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],2 s. q+ m6 L3 Q% S  {2 {0 G( Q, L! g
                  dtype='datetime64[ns]', freq='B')
    ' p( T5 i/ w# z0 D4 p# s* e5 a' l, F* v9 u
    pd.date_range('20200101','20200201', freq='W-MON') # 周一
    7 \5 s$ U& [/ T. F$ EOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')$ _& r# |7 Q! v4 p% {+ |
    2 [- l- h/ Z; u& T) ?# Z8 S) G. s
    pd.date_range('20200101','20200201',/ x0 W" q$ r. y1 U! E
                  freq='WOM-1MON') # 每月第一个周一9 Z4 _& x' d$ g) _
    # y( h3 w5 @3 U9 I
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')& C4 N% p" Y" c+ l

    $ t7 X+ @* d: _# q& Y; G1
    $ }( U7 U+ _. S2
    6 B. k: o+ Q. }( L( D" _3
    ( H5 M" \: L) y# b- Y' e4
    1 A' ?2 O: ~1 I* o$ \50 E6 y! n) O2 X0 J
    6
    9 j$ }' N& S# y( K6 [; f9 x7
    8 L2 s5 w" l6 D  K' Q0 W( \' c4 K89 t6 J- `: X5 G. V, u( Q
    9
    9 T9 N; {+ y4 `+ e% {; v' c100 V2 B# s% v7 Z, c7 _, G
    11
    $ `& z. n( f+ C2 T12
    ( P% c6 C+ `. X) D0 P# D5 e9 S" Q13" o; d+ r# x: H4 ]
    14
    7 K/ K" S9 V$ ~; ~15
    ' \, Y" O" U5 v0 K. y7 K' h/ a16
    8 h/ R; ~- q& y2 ]9 g+ _17
    . K/ ?: Z% Y' D& C: ]: N& H18, w- ^: J7 q/ \: W. X1 v- L3 `
    192 d% l5 N5 z& i
    上面的这些字符串,等价于使用如下的 Offset 对象:- Z2 X4 p# T# B% M3 U5 |! x+ j6 S$ A
      U/ t% d) y8 c. b. p- U1 b
    pd.date_range('20200101','20200331',
    , H& n+ g# S# g' L4 F) A+ W' T              freq=pd.offsets.MonthBegin())8 `& N& {; F& x
    % |! M6 M# {4 Q! g9 L& x
    Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')' o1 t) O- ]3 q& {

    5 c( b5 }' T$ ]# N! [9 ipd.date_range('20200101','20200331',' T* |( \3 g2 ^( Q3 V
                  freq=pd.offsets.MonthEnd())
    5 ~7 f) l& N3 T
    ' \: _; ^$ X8 D6 u7 |- ]5 \Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')2 _: p9 E( q; Y3 O$ P, `- O9 Q
    1 T" N; }9 \. i' P" c5 l! ?
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())4 Q* ~$ n- k, n3 ]
    Out[98]:
    . G; R$ G' l2 Q9 RDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    / t5 s- X  q# ~3 H+ F               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    + [: [6 Q' D1 h( b              dtype='datetime64[ns]', freq='B')1 R' {# W" j# W5 B  w1 X( {

    6 e, @+ e% B; Q) J0 c/ S$ R. jpd.date_range('20200101','20200201',! `# K' k) m1 p
                  freq=pd.offsets.CDay(weekmask='Mon'))
    3 A+ I8 t/ ]0 X) k8 i0 K4 a+ B. ~( K$ b9 ^
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    % P+ P/ b4 ^$ k2 N8 `4 Z
    ' @0 G* l9 [+ }0 j7 wpd.date_range('20200101','20200201',
    " n3 t) a+ v9 @" Z2 o2 ^              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    1 I# p' a' d  I0 \2 B0 n: k
    5 T0 l, `( F& ]( `6 aOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    ) i6 n7 N" P/ m( V) S* i( N. u; H; J6 w$ O0 `+ x' z
    1) P: o5 w; I8 s. R
    20 |! m7 C% m. p& ^  U
    3( l( Y  |. i/ H2 d2 [
    42 }7 X7 i( T) v  o9 y+ O
    5: M8 @! X1 R, l* m$ M5 C
    6
    ! t. Q; f3 }, \+ R7 T5 ^7
      J$ x5 S" [: ~6 A8
    : b- Q; W- M' \9
    8 O" c9 y9 V! c5 z; O4 ^+ h; m10+ \( x; t. e( W
    11
    4 S1 K! l; n* _; y12
    6 p3 g9 }9 f0 e. ^13) s, L5 Z' x1 J5 a( t6 Z
    14
    # J! \  o) V' l9 j  }15
    8 I  P; ?% v+ x16/ M' J7 H, t1 U) v
    17
    / r) u9 O; G9 h18  v- a2 B$ B# l6 M8 q8 f
    193 N# m" N7 f( O* C
    20$ R  \: d) x7 G
    21
    . K( e5 O5 y1 w7 J22
    5 K( j9 n4 ]4 T' E9 h& T- P9 J1 j23
    $ C  E0 F% y3 q; b- Z24
    % n4 S- U/ z6 _250 ^0 x4 c8 i/ l& ]* s4 D1 d
    【CAUTION】关于时区问题的说明
    2 d( x( u( n7 f( \" I( r9 n7 t7 @  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
    % U7 y% p9 v2 h& p6 s$ @. P- q9 f$ H4 a& T' @# R
    10.5、时序中的滑窗与分组8 G( v5 L6 }1 M# \  @3 V) R
    10.5.1 滑动窗口
    ; N1 Q/ X6 _6 V$ _+ p  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:3 M2 M) }. T. O9 ]

    & r5 v8 i+ C% T3 m; cimport matplotlib.pyplot as plt
    ; q; Q: J' M, i( L7 L! pidx = pd.date_range('20200101', '20201231', freq='B')
    ( }# a9 M* O! Rnp.random.seed(2020)7 M' q$ M( [9 R/ T% }6 X+ X

    6 Y; |3 D4 A8 I( u8 P9 Rdata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
    ) ^9 f# }; B: F) zs = pd.Series(data,index=idx)2 a$ c. M8 G5 k: L* m  x
    s.head()9 a) i) r; i5 D7 J5 j8 i
    Out[106]: 4 n/ j# B. z" ~# [/ J5 D7 s
    2020-01-01   -1
    & Z* K$ g" Y, c7 i: L1 ~# P2 ~* M! u2020-01-02   -2
    4 f( N1 v. Y3 g2020-01-03   -1
    ) }( k' {/ q* B2 c: G" n8 D9 g2020-01-06   -1& v" d) o  {6 l. F
    2020-01-07   -2
    5 D5 f( d: S. I& g% K1 oFreq: B, dtype: int32
    7 Z+ q+ V% ?( _1 A4 hr = s.rolling('30D')# rolling可以指定freq或者offset对象
    * k, }$ A9 Y; Y- ^$ O
    1 F/ K/ E2 F% e$ ^$ |5 d  |plt.plot(s) # 蓝色线
    6 b/ h' N) I6 K' W( NOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]" G& h7 b! B6 j1 J; v3 R& w, m
    plt.title('BOLL LINES')
    & T, N4 b# ?* aOut[109]: Text(0.5, 1.0, 'BOLL LINES')0 A2 A0 a4 h( L6 V

    8 z' H- c7 N+ h1 \' bplt.plot(r.mean()) #橙色线. C0 x& U" F$ k, j% q7 [& `3 B3 d; U7 Y( G
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]1 x5 I# y. D4 G6 Q( T

    8 o0 C( M/ {% e$ @+ lplt.plot(r.mean()+r.std()*2) # 绿色线4 e- x" m/ m$ L. Q% `  m) O. ~
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]& o7 k; A. N$ j+ {7 g: l/ L) B

    * @8 _6 I7 V1 o* m/ Iplt.plot(r.mean()-r.std()*2) # 红色线) T6 i( _. ~5 f+ U
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]0 h  N2 |; z, g0 J
    3 s8 N: g+ K6 u4 W$ E: M2 G
    1: _; _% h! B. x0 t/ z3 f; D
    2
    # a* A3 z1 a7 n. c" k2 b. \33 J' P3 x4 Y, _# M
    4
    & H" K+ ]9 s" m" n5
    ! ~" F  y5 D- S( X$ X" L, l6
    5 U- ]2 X- V  q: {$ {* }4 y( I9 ?7
    ' L/ O2 R) S  J+ {% P8 E4 w8
    2 ]5 a7 l7 @: Y2 o7 ]9  t. f9 Y- h% l6 K
    10
    . Q% ^  {0 I8 Q11
    7 }' G) h+ @: J3 p- R' n9 n12( k8 z. l, d9 ^* b
    13
    ' I# Z$ H- N, M14# M* }' O/ [( t$ X" }- m2 _5 r
    15
    ! F0 \% G4 |! @3 f: X& p16
    / U( r! r4 w( o  E4 T8 C17
    . M/ A7 o) }* D3 j18
    6 j3 H* I2 s  Z/ D195 ]1 A) n+ b2 S  n3 E* I% g* g
    20
    ! G7 n1 G0 g0 C% X) x1 r21  f; f5 G( y( Z4 x7 G
    22
    9 S  F8 F. X& _6 [4 F; z" t23
    # u8 L$ X1 ~4 B; F) D, `* j24! v- J1 r3 J9 C: t
    25
    1 r& E; ]9 S7 X) X% f! t& N- K261 d, {, R4 ]6 t8 N4 {. k8 ]2 Q
    27
    * Q' p8 W7 ]3 S1 W$ g: l" y281 Z8 ?% `' r5 V  y9 `7 F# L& F. a8 R0 }9 j
    29& [2 s1 f2 V3 m# C1 ?
    6 b4 u& ~; K- m" a$ @
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。" V" ^* ]  @- x: M
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。  y0 j; i0 t0 f( v1 o

    $ P0 u) I) V  ]% T8 V) o" f' M9 [select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]1 D3 t2 u3 c: S" o. C0 B, f
    bday_sum=select_bday.rolling(7,min_periods=1).sum()8 w% E" a  f3 p" e- I* ]+ O
    result=bday_sum.reindex().ffill()
    6 W: E6 ~2 s- T+ l) ~5 dresult
    2 N% N; ^! k" u4 W; G) q1 K, z; D: C" h8 t
    2020-01-01     -1.0' T7 B0 j- v( l: O7 t' z; I
    2020-01-02     -3.0, J  J! c" q- X8 K+ y; p( Q# [$ z+ U
    2020-01-03     -4.0* ?; d& @/ }$ G6 F8 s/ F
    2020-01-06     -5.01 R2 z( u4 n4 s
    2020-01-07     -7.0
    * r) i+ R3 ?; Z5 {# x/ ^6 m              ...  
    % x! i% y5 Y) Q: D+ p2020-12-25    136.0% E; P0 \7 K" {1 W& X8 \6 q" W
    2020-12-28    133.0
    5 @9 ~/ x( n9 h% f7 n- N0 q' |( k2020-12-29    131.0
    0 `4 O+ n! o& Q5 q6 z! I2020-12-30    130.0, t! E4 p% y3 ?+ t
    2020-12-31    128.0
    * R. F/ F8 k+ {/ h$ C: R: qFreq: B, Length: 262, dtype: float64* u" o$ w- @3 {
    * e( ^' A0 b/ _; i2 l9 ]+ B& E
    1) c, |$ r  }* O! \
    2  P: o* ?, e6 M4 Q+ I4 p+ v! j
    3
    $ k" K$ |/ L- d8 Z+ M' C5 k6 j/ D1 @# F4
    & c) D7 V6 K9 P( @51 K& \* j- d$ k5 j( f" C" f) _
    61 T- l9 b2 R, m; t  V& V0 G
    72 ~, C$ N( D. Q+ P9 _$ k
    8  Y4 P4 @7 |; y
    9
    1 P- U2 \  f8 @& M* V; F* M; ]10) n* r( n8 y2 g) [3 R$ _9 t
    11# h+ a" z2 r, T
    128 |9 Q# V# \  f: B4 E% @
    13
    ( }% Q5 w/ J+ w  u: y4 V4 d. B14
    4 T& m- |' H( A1 `$ z! M15
    0 n) ^0 K. c/ {, z* [4 ^16
    . _* Y  {5 U' ~' J1 {' H) ^176 Q- O' T0 y1 p# R( M# s; S
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。; ^3 s, T/ y" H  Y+ H* P
    / `3 Z& L6 {7 s) @2 o
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    - O/ P+ O& G; J6 T8 a/ _
    . H' s' w& x" k9 ~7 Js.shift(freq='50D').head()
    0 s  e0 h- o- T3 _8 g4 sOut[113]:
    / C; q* S" _. [  g: m2020-02-20   -1
    0 T% ?5 k: c: w; W' A9 J( L  H2020-02-21   -2- {8 w- G1 k& Q0 Z+ [# V7 H
    2020-02-22   -1, M7 Y6 |3 q" e8 F9 t
    2020-02-25   -1
    ! q/ e, N4 r: G( Z2020-02-26   -2
    $ ?/ i+ k7 }; Q4 K& ?dtype: int327 ]( ^- {; r# u
    1
    * l; f3 [7 }/ f! o; g  ^2
    5 M7 c- o. Z6 l  }39 K+ |8 q$ k0 |7 Y: D6 T
    4
    - ?8 w7 }' y/ t5
    ; q2 T/ q+ n- d9 ]% H6: c+ e' l& _; k$ B- `
    7! @! V; D1 W7 x
    8
    $ T/ R: G. c# X7 m+ U) R+ p  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:& m, [- E7 |5 u  U2 G; U

    9 K/ c3 X, a2 C( I5 mmy_series = pd.Series(s.index)3 Q! c$ f6 q$ j$ N) F$ V2 Y
    my_series.head()
    5 _* r# `3 n! A: l8 M, NOut[115]: 4 @+ d* d- d+ c
    0   2020-01-01
    / b: z+ ^2 C0 Z5 S1   2020-01-021 R: b: F+ h5 d; I& Y& ~% z3 `
    2   2020-01-03
    . M4 P7 l) t8 ?3   2020-01-06: c0 U$ i4 w4 }9 ]
    4   2020-01-07
    ) v7 b/ x$ f+ ~/ c7 e' adtype: datetime64[ns]- {% s( ]# Y( q' v

    , T: N  o- f1 c( ymy_series.diff(1).head()1 B6 k$ ~  Q! }: M+ m, N, l1 Q- I4 F. g
    Out[116]:
    * m0 T* @. n) h$ f6 b( `0      NaT( q# I: i( S3 F
    1   1 days% E. I! l- {3 l
    2   1 days% i% ?/ C. b1 h% [  {1 C6 l3 r
    3   3 days
    ; }8 J) I+ L# L1 L4   1 days
    ! \5 G) R5 S7 `2 q$ odtype: timedelta64[ns]
    : O$ k+ u9 R, R" l9 v) L' L$ l8 [, k* |* \: i
    1
    & d* i/ `' N% J* P" \$ F, w$ Z2
    / W0 o6 ^, }' W. q37 o* ]6 F! H8 L5 m
    4
    - h$ ?- V* p1 t3 H, M5
    ( Q# e/ X6 e# G+ j2 i6
    % W( l) z) t# [4 {7  j, l' v- h4 i+ K7 \
    8
    : M" r: W& j  S+ B9
    , T9 C# A6 P7 h2 J10
    # i% e) M9 D( w  I1 J" L11
    5 J, ]1 K9 H# ?& I3 h2 i3 u12
    % n6 {5 i+ c$ P$ v' E( e13# P8 z2 ?7 b. q  W" _
    14
    6 u+ q+ r& N% I5 N7 L) Z3 x, C0 ]; G9 h158 K* P4 E: m  B8 B& c( t& @: e2 q
    16
    8 d1 ]7 x# J  ]8 K17
    6 Y  I* a& d% Z" @8 J  H& ?18
    ' l: P, Z- _% S( \, X3 Q10.5.2 重采样0 q2 g3 U) S# p+ T8 W
      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); s& p6 M- `+ I3 m/ e
    常用参数有:
    * H  j5 P& j% _6 G% r: H- \* M! H+ K# G8 @: L
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象0 y( c  p* ?0 I$ z! s
    axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    ( E' z+ O1 \8 b! ~! F3 x1 [( bclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。  D3 D6 V7 s, E7 W  ?
    label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。. h% P+ a( I: c: M( s; ]; m
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    - n' a* J* t1 `  don:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。( \) F; g) [  f: p
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。2 @# F% T& A" o: g0 l
    origin参数有5种取值:% D- u- q& b8 r- z8 P5 z4 `! x
    ‘epoch’:从 1970-01-01开始算起# \; O' y. O" ]5 h) x! w
    ‘start’:原点是时间序列的第一个值
    + I2 |* A1 X) C% e$ m8 `' o) Z‘start_day’:默认值,表示原点是时间序列第一天的午夜。) t* `% }* X' }5 R7 h
    'end':原点是时间序列的最后一个值(1.3.0版本才有)
    + ^# x6 @" c! U‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)5 B) s3 n% Q4 X8 v$ @& w2 w* p
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。4 o* g' @1 Z" f3 @; F4 ]# w! z
      closed和计算有关,label和显示有关,closed才有开闭。
    9 o+ l! u3 W' }$ u- z5 K0 B  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。$ O2 Q: [; |% h2 |, a; p. V

    % h+ Q0 x: P0 V# `' i! x重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
    " ~+ O2 X) L3 d: os.resample('10D').mean().head()
    0 w$ l; {9 V, w& s+ `Out[117]: ; [  {" g/ j/ k$ m
    2020-01-01   -2.000000
    5 j! h- G- x- |& T# m* h7 x& M2020-01-11   -3.1666679 r0 C: |8 _' s& `1 Y
    2020-01-21   -3.625000
    ) @6 P2 K! ^1 K8 ~6 \5 [2020-01-31   -4.0000005 }! y% x4 L! |& w" ?* w; [8 @
    2020-02-10   -0.375000
    $ t9 \7 m# {4 e& U& _4 e7 \Freq: 10D, dtype: float64; E. A% I1 u; R  Z
    16 O/ O- r! I3 ~. r7 q4 I
    2
    $ Y/ J$ R( k+ s: `" L' y0 J3
      G, N( ~2 E8 E: i' d( H( z4
    2 f: E8 o3 s$ G' U+ `4 |& b- R5
    $ ?& R! {5 i& a, i( @0 }( n6 S# W66 u$ l' l9 \! I- ~
    7
    6 R) ~5 i" I; w" Y5 k8
    # r" W/ q  G0 `! ^; o  U可以通过apply方法自定义处理函数:: Y3 M/ i3 [: J9 M6 K/ `0 m9 b. h/ I1 l
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
    ' J+ D0 e! Z( e
    3 q6 I* `! g% O. L2 T! zOut[118]: & l8 R% G% ]1 I  s
    2020-01-01    35 V! P' R( K* j/ |: c
    2020-01-11    4
    : Q) D6 ?, l& E" a' h/ G2020-01-21    4/ F. ]* c; ^/ O7 w# S% M
    2020-01-31    2
    ( u3 P4 d6 w0 ^: S! t* p# {2020-02-10    4
    7 O- D! W) F% J2 R7 W$ w* tFreq: 10D, dtype: int32: C+ C# p7 v& G& Z
    1) l. O7 t% |3 _. M2 T, t, G; A7 v
    2* v$ M, E% z1 T& c9 u: P" X
    37 H5 t, D% K" L2 W. Q
    4; J( x, x5 _* G3 O- B, w' _& O
    51 B" i& ]. |4 S+ r' L/ ]
    6
    2 }, p" n& e6 E2 v- @4 C$ v7" i4 o9 y6 V7 F( b; |) l
    83 h# W# N9 ^5 K
    9
      `9 e1 j1 y3 Y/ }- _0 _  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:3 B0 @4 f) e" J5 N- y7 J+ T4 l) M: a

    1 f9 {. a9 D$ M! Z4 zidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
    6 z$ }! w( }" f, Hdata = np.random.randint(-1,2,len(idx)).cumsum()% c7 _8 h* w9 Z" |* h& q! `2 I3 ]
    s = pd.Series(data,index=idx)
    4 D5 A* b" ?: g9 ts.head()
    - n( d2 x$ n  a6 [9 t' O! D- R0 R' z
    Out[122]:
    0 x/ U7 O, A3 c! O6 t2020-01-01 08:26:35   -1
    & o2 S# A3 w7 B( R2020-01-01 08:27:52   -1/ z8 Q& I$ ~7 r( G8 [8 i
    2020-01-01 08:29:09   -2
    2 X, M. o2 |3 d' ], Q, Z0 O/ k. C2020-01-01 08:30:26   -3- y* C* q0 v+ Q$ i- o( F/ {
    2020-01-01 08:31:43   -4
      |# s- z* N# ^( R+ r, S1 cFreq: 77S, dtype: int32
      k% V5 P% t1 B' u) R: K- z! D* W1# R, h- ~9 _# T% R* w
    2
    * C. d3 ~. Q' L0 P8 [& t' `3
    6 O4 r) Y* R; e/ p9 T45 d* c7 Q# x7 ~5 C
    5
    " {- J9 C4 m' S& Y1 t7 B5 G' v6& r! u$ |9 _& y2 b3 T7 _" S
    74 {" x; v/ R, i+ r' U
    85 ?4 O' I1 I% l
    9
    . d- \2 ~; q+ A4 U102 ~- w% h* v' {
    11
    5 X) K, k0 w& V. z12
    ' @. g3 M' U4 q, t$ }) s; i  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
    5 ~% W) w- z/ O) N# o0 \# _& a  D; @: h
    s.resample('7min').mean().head()
    , d1 x! [  j' L; dOut[123]: 6 l: {9 T$ @+ A1 g2 U/ _/ W
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值9 F7 d7 i# ?2 f( J9 E- a0 L
    2020-01-01 08:31:00   -2.600000$ Q1 y& `8 E! j
    2020-01-01 08:38:00   -2.166667
    & e/ N2 q8 J& F0 J2 s2020-01-01 08:45:00    0.200000( O' c& |5 h- I) Z5 o% ?
    2020-01-01 08:52:00    2.833333
    3 y+ n4 D% E, D* q1 G8 s0 s/ ZFreq: 7T, dtype: float64
    ' t& O& Y' m7 q) ?. v, q13 m$ {/ S- r3 I: m( b9 `
    2
    . o2 c5 c9 |6 i  r36 f' h+ }9 i% b7 `( H; A8 Z) h! Y
    4: C$ |( ^; a2 S7 o
    5
    + Y. z. E( i# {9 Q% N9 Z$ {% h6: G: a( k2 b) b% j) [" i5 x1 x7 v
    7/ N7 [. f" T) C( B
    8. t. ^6 r5 _" a8 C% R
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    ) H* {6 ~' X; M% N- K6 N* _6 l( b1 A, O; L' t4 C
    s.resample('7min', origin='start').mean().head()
    ; |+ r. M$ O6 |' u- R* e  l. W( dOut[124]:
    ' X( f3 S+ Q+ P' {% J2020-01-01 08:26:35   -2.333333( ~4 w7 E) O; D$ h+ |0 C5 `+ Q
    2020-01-01 08:33:35   -2.4000009 G' t# d' N" x  H1 C& u
    2020-01-01 08:40:35   -1.3333330 v7 b# a3 I# \- q5 [% i
    2020-01-01 08:47:35    1.200000; e8 ]* |# }) \2 n
    2020-01-01 08:54:35    3.166667
    9 o' m8 Y- q1 _$ |Freq: 7T, dtype: float64* {( o# ]" Y! S8 ^! A) ?
    1
    $ u- q5 d. Z, h& p) j2/ z- w: @& L2 k8 ]7 W2 F+ {+ A3 i% w. o
    3" z0 D5 s* x% C6 Q
    4
    * b7 M( Y: e( L0 Q; u. `/ O5
      z1 g2 c; A6 G0 ^/ p- r6. }! X' q6 g9 B
    7+ m+ E1 j; A0 k! L/ ^3 U
    8
    ! d3 |/ p7 H, M& i# V  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。* t5 w0 @7 \  Y" ~/ ]4 ^) D% s9 g8 R
    $ Q( Q6 Z- d" k, g3 s% z
    s = pd.Series(np.random.randint(2,size=366),( S1 P1 ~# m) A1 O8 y
                  index=pd.date_range('2020-01-01',* @8 F9 m2 i, [: i9 j
                                      '2020-12-31'))9 M2 k* C$ U. `, F% h5 P

    ; _/ R. e. E0 ?
    * ]/ P9 \7 [2 e4 Zs.resample('M').mean().head()
    4 T+ N8 C2 u' lOut[126]: 9 _. q) v8 O# O2 O$ l( Y/ }
    2020-01-31    0.4516138 V# ~! H( g, a2 v
    2020-02-29    0.448276
    * s  ]- ?! [# \( R. K2 s, W1 B2020-03-31    0.516129
    " k, ~/ G# V' {$ ^2020-04-30    0.5666670 Q2 a& r! \; @% V- P
    2020-05-31    0.451613
    / n4 D1 Z* `# C  sFreq: M, dtype: float64  k0 m0 R  ]3 o) E0 B7 X
    2 q9 C2 z6 I# P: J, M$ Y" v
    s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
    " r& i4 a! U- D  {) c& lOut[127]:
    ) t' R2 S7 x; V/ T: S2020-01-01    0.451613. @9 A: M/ v: x, ~2 `. t* }" s
    2020-02-01    0.448276
    ) f; Q1 v. O+ i: C' n' _8 t6 o2020-03-01    0.516129
    " c0 o) ^  X1 i# ~: L2020-04-01    0.566667
    / b* e  A( ?$ t. m' ?$ y9 N8 y5 C2020-05-01    0.451613
    4 ?8 [* A4 F) y/ u0 j5 QFreq: MS, dtype: float64
    0 d5 T" [: T( e1 f% i9 M$ R, S  }
    , `1 p! q1 H) W9 j$ {1
    * R" P0 J' s2 Y9 r! o; z  |2
    0 p! U& }% R7 {. P6 h3
    : [2 q7 e, B- Q2 f( g4; j# s2 h2 z9 h$ {
    5) ]9 U% o/ R+ G  L) `4 U8 I
    6' D' k$ ?" A  I$ s1 R( I2 i
    7& ~4 u$ m8 M% z8 e4 G4 r# a
    8
    $ e! a) J. H# Z' O* E2 Y9/ {4 p& d; j  u% m
    10
    2 p! ?! h+ I2 r5 G( |1 S- P11' {3 @5 b" N! _+ c
    121 u5 Q/ l5 x4 `; C
    13& }# t  N. }& E( d5 M
    14
    : @: a7 S6 |6 y, I3 N0 ]: U) V15
    ' ~' m* l7 `! o6 m' s: Y% r161 t, k# Q2 K9 i* {5 T0 ]3 H
    170 H/ a$ \) y; i5 N5 @
    18  J% R. ~! {" D+ }+ T/ `" m+ Z5 y
    192 `! ^- {, j. u
    20
    $ Z( c2 S' {" q217 G; A3 P( y) k) Q- c/ p
    22! b9 X1 f4 _6 i, e
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    ; v- \% Y) \& T0 Zd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    ( g, A/ Q0 H6 y! A- W     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    9 P) k0 T( z# y# i7 x( cdf = pd.DataFrame(d)* `& h$ T' j( z
    df['week_starting'] = pd.date_range('01/01/2018',* g( G# R  u7 K1 V
                                        periods=8,
    - s; |2 A5 d3 a4 ~& _2 Z                                    freq='W')
    : A" U, @! C0 D( j; N- @df
    - v/ }- ]3 E" h4 r. E7 n   price  volume week_starting
    ! `9 {" \8 G8 Q% K# _! L( j0     10      50    2018-01-07
    0 R& c# x: K4 X4 M1     11      60    2018-01-14
    3 l" w/ T; ^; B2 c) V9 U: r! L  u5 V2      9      40    2018-01-21
    , ]& ^. h2 w5 P0 y6 g3     13     100    2018-01-28
    * w. T/ k/ _, Y9 c" a5 k4     14      50    2018-02-046 ]% s3 f% J: {0 R
    5     18     100    2018-02-11
    9 @1 G+ |8 \% }! M8 e8 }/ e6 p1 A8 t6     17      40    2018-02-18& x$ d+ b6 v: }) n% w9 w* [7 q
    7     19      50    2018-02-25
    5 h- \+ Y! C# M& Ndf.resample('M', on='week_starting').mean()
    7 q  I& X6 t* I8 w6 \+ R               price  volume
    * ]% L9 b( k4 i3 t6 E8 B4 D: G- G  Sweek_starting
    $ y$ X  I7 e; i" }4 `1 k2018-01-31     10.75    62.5* s$ `) c, E7 h0 Q
    2018-02-28     17.00    60.0. M: }5 Y- \- p8 J+ O
    , F7 s' C+ q& `7 U! D% @
    1$ H& a' K: X9 x5 x+ ^# \
    2
    ; w/ x9 A1 P& j0 q* f3
    , X/ T1 Y4 D: ^. g( P) |7 ^4 \) w4* F7 T7 E- R$ |: A$ J
    5
    - O3 B! n, D' w% K# r' Q3 @5 `6
    # s) K4 R5 S. e/ ~7* p4 d8 w1 }! w! |7 Y* U/ t, G
    8
    $ E1 U) b* S5 [) e' K$ H1 V7 J99 f7 C& w9 ]' c
    107 O5 T% s" y9 ]5 V6 a9 C
    11+ D6 r) F9 U" V* w( [% v! x  V: v
    12
    8 X0 L$ A3 t) U5 v13
      ~, U1 T0 ~2 t4 c/ d" U6 t8 _14
    $ b/ A/ W5 a' F) N2 H5 k5 E15% h/ Y2 c/ D. m
    160 _; ^$ p6 s4 g; u* ^
    17
    7 N; C4 L% m( c. _6 |# R189 Z( ]5 r! g; ~9 H6 D! T
    19
    # M# X6 [+ ~$ j  x& |9 e20
    5 ^7 J0 j2 h7 K& a, ~, d21
    3 B4 J: e2 h1 ?9 |  Y1 d( S对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。$ X; z$ Q7 B$ c
    days = pd.date_range('1/1/2000', periods=4, freq='D')- t! M7 b) H& z: P
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    ; C* o/ f  r2 s+ `  u; t( b. }      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}* ~9 b( Z, b4 ?6 _; o2 e
    df2 = pd.DataFrame(! n$ u" ^1 V- m  {
        d2,
    9 ^" ^: u, t0 K- v* V    index=pd.MultiIndex.from_product($ e1 k! K$ M! P. c3 ~
            [days, ['morning', 'afternoon']]
      ?. _6 B6 Q$ D9 K' F    )
    $ \: r! s( U; `4 s)
    3 }0 D# P) u& f, g6 z) X+ Fdf21 a6 B" Y' l2 L
                          price  volume
    / v( f+ y( o" ?$ Q/ I! F. Q2000-01-01 morning       10      50- ^( N" ]# c/ r  k9 r$ p
               afternoon     11      60" M/ z  ]& {0 f( b+ t& o, J
    2000-01-02 morning        9      40( Q$ p2 f5 L. ~# m! w
               afternoon     13     100
    0 o# ?& g2 f5 N( M; l2000-01-03 morning       14      50
    + ~0 U+ d! J2 Y. s0 ~# ~; y           afternoon     18     100. m' k4 T8 W# V& L
    2000-01-04 morning       17      40
    8 H( y7 a: k+ P' [! P' }" B1 u" T; K           afternoon     19      504 p9 N* p  N) E7 `$ U, }3 W# y& X  z
    df2.resample('D', level=0).sum()& W9 h  }) G. G0 ^
                price  volume/ C5 M" \+ L: u' K( C( h
    2000-01-01     21     110
    # O7 ?9 p$ t1 g& |) ^2 U2000-01-02     22     140
    8 {) i9 }- w: G2000-01-03     32     150
    5 [# F& {8 j4 ~& j- E. w0 b2000-01-04     36      90$ a6 d9 g; P" N/ y" P! M2 m/ i

    ) V/ I2 i5 @9 z; B( e1
    5 y+ Z" T; Z( e" @25 z) \+ D! X8 z* Q3 w
    39 q3 _$ k. y# u+ z) k! ?
    4
    8 a' u: G7 |5 M5; e; p$ i+ ]7 p
    62 c: N  L4 ~1 c5 i/ Z$ D/ C5 V: V
    7
    0 m2 F8 b( D1 B% h8 C8* |+ `8 D" M. B; y' i9 l
    9
    ; L  F' ^! I# P. Y% A0 y- R10/ m& d# P7 L) ?! C9 V# W/ O
    11
    4 T0 Y! [1 ?" X121 I/ y6 U$ B! S8 @6 U, w0 \
    136 ]7 N8 M  ]9 N! U! j' l
    14
    " |* o% M6 C- Q% d15( {8 v- ~8 j7 g6 }
    16- f5 L- ]/ d& V/ Z. {
    178 \  x6 y2 W( I& ^% i: d
    18
    7 {( W2 r+ `; y19
    / C( I/ H% J% S2 _! v; K8 M20* v4 v0 F. o! h& H
    21
    7 @5 D8 T( x/ Z2 t$ m22
    6 _4 O9 Z, ?, D  Q! c& {23  B+ e8 K7 ~! i1 f- ~+ [2 G
    24
    1 w4 h4 x: l* {0 J: r( U$ i+ `25
    ! X# k  v- |6 `- I/ T, z根据固定时间戳调整 bin 的开始:- y' y/ ~. G! _/ @$ c( i" U
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    0 Z6 l' y/ u9 N9 G) erng = pd.date_range(start, end, freq='7min')
    4 Z/ h+ n" \- e% n" Jts = pd.Series(np.arange(len(rng)) * 3, index=rng)/ t- [0 E0 L7 s4 T6 Z. k( V. V
    ts; M. M! }/ [8 ?- {6 ]
    2000-10-01 23:30:00     0
    + D9 b  S7 M! v* ]2000-10-01 23:37:00     3
    , H) q: J8 U. @+ n) t# W/ b2000-10-01 23:44:00     6
    3 C* U9 o% E$ P. e7 H2 q2000-10-01 23:51:00     9: r5 i, o5 z; R( t! Y
    2000-10-01 23:58:00    12) [& E* f; }* @" G, ^2 j: P2 v9 u
    2000-10-02 00:05:00    15
    - ^, t# _4 D0 B2000-10-02 00:12:00    18% P" u5 m5 K$ ?  E: S
    2000-10-02 00:19:00    21
    7 w( H4 Z" p/ v2000-10-02 00:26:00    242 t+ x/ |' C- ~; a* Z1 V  ~
    Freq: 7T, dtype: int647 [- j- W# J% F

    ! _; S: r8 g2 @' P8 u) tts.resample('17min').sum(). t+ p8 U6 k) R% S1 c" A
    2000-10-01 23:14:00     0( M' k9 b. J: g1 e- m6 t: m1 V
    2000-10-01 23:31:00     9
    ) A- d, h, Q+ p2 Q. _3 c2000-10-01 23:48:00    21
    1 j: K3 R1 L, _( q- `, v2000-10-02 00:05:00    54: F3 f, [5 `- i# M/ c, R
    2000-10-02 00:22:00    24, }0 S; H6 U7 u! K3 u
    Freq: 17T, dtype: int64
    % k5 O4 F$ @* Z2 I; h- ?
    1 n( U8 _+ H: @& E# Tts.resample('17min', origin='epoch').sum()* M, h: U" N5 y9 c
    2000-10-01 23:18:00     0' \* a. d9 U) w- K2 z
    2000-10-01 23:35:00    18
    + E: X- _1 J1 I. b$ X( A' {2000-10-01 23:52:00    27
    4 `! G. d8 Z9 Y9 ~2000-10-02 00:09:00    39; o" c7 m; y! v, Z5 e; H" I# N3 ?
    2000-10-02 00:26:00    24" e' Z, Y6 {) r$ z' G4 \" L
    Freq: 17T, dtype: int64
    0 g& ^) p/ P' h2 \1 I. t3 T( F7 e4 a- H$ y9 a
    ts.resample('17min', origin='2000-01-01').sum()( c; Q4 f* X0 ~; r- Y6 ^- V* r+ z: |
    2000-10-01 23:24:00     3% \0 p( H! B, L; _
    2000-10-01 23:41:00    15
    ) |' Q3 u. b* n, U2000-10-01 23:58:00    45' M5 k7 ~. F, t
    2000-10-02 00:15:00    45
    / z) @: `! c7 `Freq: 17T, dtype: int64
    - a# V5 C% X6 L' i+ ]1 r4 `8 w
    * }5 [$ R3 U: i& c1
    6 R# F3 _2 s0 o2
    % U/ E3 Q7 p+ j1 F+ t; w! D& C3
    7 a6 r! r+ e8 R' E7 W; p' D4
    " R5 Y$ d# c/ V5% N$ n. x1 E" B8 ?! I7 r
    65 E& G* m- I  S7 x. s6 k- ~' b* U
    7
    $ T* A9 o% C4 |+ N+ {4 y. M8
    3 U$ e8 d$ S/ z+ A! M6 f  W3 c9) D' g' K! i- A! j9 ?: N, @$ L/ s/ w" ]  b
    10
    # n4 J7 H  L* a0 j2 D0 x) A11
    6 v3 C7 i2 |( q2 j) E. u12: s! w& k% ]7 a
    13
    4 ?7 o+ p8 D0 ^) f8 x/ [2 ^148 y; a" g: y+ J  N0 e/ L5 m/ y
    15
    % Q7 M! ~3 U3 S7 M2 J5 Q16
    / S2 m5 y, ^- d& u17
    5 ?& o- x! _9 q- k7 ~/ s18
    , D4 e7 @9 [8 l0 T( Y19: J8 h; r0 v/ S% x8 ~
    20
    1 d* K4 j/ E4 B+ J/ k5 ?21
    & G; a1 l1 e  b! Z, h: ?! y$ i9 i4 U22% s2 J+ P8 T% J* J' B
    23
    $ V/ H# |: @" k+ l8 I! t& G242 y* T/ i0 ]  r  q/ D+ ^
    259 R6 X6 D6 k! ]% j% E7 C, P  Y# @% s
    26: J  n! j5 g( Q* L
    276 k, Y: q, O& I' A9 _  Q
    28# v0 b: r8 |( w7 F
    29& o: t6 C. X; b* e* O0 z# I, `; t
    30! Y2 D; w  l% P# Y
    317 r: m- T5 [2 A. I$ ^' J3 n6 t
    320 ?; s9 \4 s3 a+ Z
    33
    6 Q1 U7 G' Z& C$ H34. z4 d/ K% f8 ?0 m0 M
    35
    . F" S) J# r- {1 N' A9 J36
    4 B& j% \$ @8 F0 M6 {: k* J* _: d37
    / X) w: W  b2 z7 Y如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    1 \) `  N7 I  U: E& l8 @ts.resample('17min', origin='start').sum()
    5 W& {1 L3 {0 S( ^. Ets.resample('17min', offset='23h30min').sum(). v: e, L& m0 E9 t! W% c$ ?
    2000-10-01 23:30:00     9
      G6 T  q; u7 k1 I2000-10-01 23:47:00    21
    $ f3 Q+ E8 A6 d+ T2000-10-02 00:04:00    544 n$ G; N3 |* D( s5 p
    2000-10-02 00:21:00    24, R+ [0 Z/ x5 ?7 a) E6 r) H. _
    Freq: 17T, dtype: int64
    ( m6 R, H0 C3 f$ k! |1 J1( B5 ^0 k  L1 t* n" C/ a
    2* k# P) q# P% s( ]" l* O7 z
    3
    8 z5 c: Z/ Y" E% p4
    $ x& e) S! B9 z* K) e. w! D( U5
    ( s" G$ R' C% Y8 v" D& {9 i) t; ^6
    1 Z$ z/ U0 \2 C$ a" a, e7
    4 a7 x( F. W: a10.6 练习
    / `% M% ]6 z% pEx1:太阳辐射数据集( G$ }9 h9 j( h3 K5 X6 w
    现有一份关于太阳辐射的数据集:
    ! k4 Y! n6 k9 H0 F* Q2 G$ f
    + O/ A2 m( B, \df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    4 m7 L+ P( v, x+ \df.head(3)$ v2 b% Q- M4 s

    $ ~/ o- u( E  G& y' g" N. i: L/ A3 p7 \Out[129]: $ ]) Z+ H8 J1 ]! T2 B8 x6 a! F& J
                        Data      Time  Radiation  Temperature
    / U8 y5 t7 [2 h% _: Q, P0  9/29/2016 12:00:00 AM  23:55:26       1.21           48  [& Q' }# ^( Z& J) ]+ v
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    9 w7 [( q6 j% g6 A+ B: |2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    & H3 T1 V  v$ ^8 S# N10 z4 D$ C3 v5 l. V2 H& x) X: l
    20 v# I7 f4 b3 k
    3
    2 x6 L) a( M7 S/ y1 q6 y4
    ( ^, ~# ^9 }2 m& d. I# M5 a' t5
    ' H. k) V) M0 r# M0 b; M) d6$ s0 P" X- P" `/ \5 D2 y, e* ~
    7) _8 M2 C( M* a7 P
    85 u, Q! _$ C% C3 G8 E
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    / [/ i8 [" q. z1 ~! k3 Y每条记录时间的间隔显然并不一致,请解决如下问题:
    5 j- t! W* x" k- m/ U+ ?0 T找出间隔时间的前三个最大值所对应的三组时间戳。
    * d2 N, j( V3 q' _是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。) A% p2 {9 g. H: O* c+ A( k; |3 l
    求如下指标对应的Series:! S3 E! o1 [1 Y" ^' Y
    温度与辐射量的6小时滑动相关系数
    ) r% I6 P3 j. w* m以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    3 e: X, d. D" i每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    3 Q  z- P( K* L4 h8 A8 N, `) Himport numpy as np
    / Y6 X# N" h  r* F; jimport pandas as pd
    ! C+ K1 J% h; g0 T8 o1( `7 c/ u' W" [4 T+ f3 M4 v
    2- E" w+ `/ h! i  w* G! ^1 l( u
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    : V- \: [. n4 U" I0 l. Xdata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列5 w% U0 P3 x0 h
    times=pd.to_timedelta(df.Time)
    . p* p9 b) {8 Z  C, Q+ x5 T# odf.Data=data+times
    3 o5 I$ F8 o) ~2 `2 P: F: M3 [del df['Time']
    6 o1 r* J/ q; k) P* \% e- hdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    , ~# V, @. V0 l" d& e. f+ Wdf
    4 U- f; y4 e6 e7 p5 [7 x7 ^0 F0 G/ p7 [                                        Radiation        Temperature& c) U' P" _+ T" \/ j- d6 p
    Data               
      y9 g% z" ]( Y* `( V" V2016-09-01 00:00:08                2.58                511 a3 X# K6 ~/ L6 ]! Y. K1 e
    2016-09-01 00:05:10                2.83                516 _; m9 o; J  u/ |+ W1 f( U; f9 ?* N
    2016-09-01 00:20:06                2.16                513 |8 D- e; V- e, B; [* I0 L. o- [
    2016-09-01 00:25:05                2.21                51
    4 ^+ w, w: L4 O2016-09-01 00:30:09                2.25                51; Y2 V+ [& J! ]# I) z' s& ?2 K2 ^
    ...        ...        ...
    . u0 C8 i+ @, f3 @" t' W# L2016-12-31 23:35:02                1.22                41: q+ |8 {6 K0 m+ W8 F  E) M' h& j
    2016-12-31 23:40:01                1.21                410 a% ^; D$ T7 H7 I' L1 _
    2016-12-31 23:45:04                1.21                42
    4 @2 `/ _/ u- O2 F- g9 n9 z2016-12-31 23:50:03                1.19                41: T- n  i. @. Y5 g% B
    2016-12-31 23:55:01                1.21                41
    . V$ @. E+ b1 M- d9 w
    * M5 `! A" b" C9 |) M1
    - C7 G( j: q8 S0 f2
    - {: W0 m. \: F( `: j( A3' g2 s7 a0 g" Q8 c5 A# X+ P& b' l
    4
    / Q, @4 M" q4 F# L0 \) \7 {/ m5' Z& z% s, j- s+ C
    61 o* E+ ?8 H* J* ?" N3 Z9 X
    7
    & x% s* I5 a+ W8
    ' p" y# {3 b8 z( F2 S  {& }93 @) T# g! H8 c7 i& t
    101 u( b4 X+ w, o- h
    11
    ) {- l; x% {% u12
    1 B' i# [* v" q8 n: \( F& w  Z! F137 y* g# l% L9 g: s
    14. {/ q$ t( u, k- O
    15
    6 g4 N: r+ t: z. y169 J! a9 U: t- ]- v# s4 b8 G
    177 k* x; P6 G; S
    18
    3 G9 d7 ]* K2 c: ^7 A) T19+ ]7 F7 \! k: w# a, k$ i, |1 a
    每条记录时间的间隔显然并不一致,请解决如下问题:$ K1 s( p1 \+ }3 v% W7 n  e% B
    找出间隔时间的前三个最大值所对应的三组时间戳。
    ( Z. g1 |- a: {# 第一次做错了,不是找三组时间戳
    3 i% T; ?1 v% C* y5 ^; p( H  Pidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]% z* m$ c: H7 {3 @5 u3 O6 M
    df.reset_index().Data[idxmax3,idxmax3-1]
    ( B) l& m. Q% c, ]% r; c. O8 d9 E) K- d: T% V4 \
    25923   2016-12-08 11:10:42
    8 E% H" \% m" w6 X6 V7 t% L24522   2016-12-01 00:00:02
    ' @6 ~& A2 t* K$ H& E2 x/ p7417    2016-10-01 00:00:19
    8 t/ ]+ M9 W5 ^! }$ LName: Data, dtype: datetime64[ns]
    % @3 w% _5 r# @3 a  F. F9 b11 z" W/ _" q: Q" m  M6 b
    2
    7 |3 Q$ q2 y: c3 C3 i. M+ s" }& L. ^7 @3( D9 B. c3 N6 P  D/ o& X
    4: G. X) t. w1 {) [
    5$ N, z* a# l7 j- c# Z* P1 @8 H
    6# I3 ^8 p, V( a/ l
    7
    % q5 F1 x+ o& Y8 _* ]0 M8
    ) o1 h- j9 S# K$ ^( u  q3 k9 Oidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]" ^% f, @- v* M2 y# e" T1 y  ~
    list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    2 g. Q# y' f/ i. m
    ) O7 z" n+ ^, U; O9 w[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    9 N  e3 c4 Q3 M: c (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),0 S: O* _% H+ Q: V8 L- N! Z* w+ S. ?
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]/ ?. v( f2 x$ @; N% [
    1
    # B& O$ B9 N' d: {# H2
    : g# \! S/ d8 W. G8 H* f% g" Q: ?3  \! s8 e; p2 V; h! t! {- K) [
    4
    ' I1 g, T1 D% q* G% X5 K) X5, s% `- k! p, U; k
    61 m6 W( X) n7 Q" k* {* X; S
    参考答案:. o) @8 Q9 [4 \' |2 D4 W0 x

    2 O! u4 X7 r& H9 x* Z0 [5 v2 ss = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()6 g# q& K! D4 {8 D- B
    max_3 = s.nlargest(3).index- H/ z& N8 k" ]9 J
    df.index[max_3.union(max_3-1)]
    " }6 o- q4 Y) ^, l8 @, d
    - l2 q7 N- r! f- l1 W# w/ Q4 ZOut[215]: 7 f# ^* o/ I& y
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    4 g; Z3 B0 Z0 O' }6 R/ g               '2016-11-29 19:05:02', '2016-12-01 00:00:02',. z. U) G1 s! j8 W2 `# I' Y0 A
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],: j( l8 X+ j4 \) }
                  dtype='datetime64[ns]', name='Datetime', freq=None)
    ; ]: T9 X/ t8 [; P1
    * c! [* y: f0 Q/ u& Z26 P+ X& P5 x9 P# U5 L, C$ k
    3
    - E2 N6 o+ ~/ j) J, j4
    - n1 {/ G# W! l1 v7 L( E1 H5) \0 j" A3 @. O2 {5 s
    67 l5 f3 L7 l! ]# E/ |4 C
    7, T1 i" I- s$ ~4 m9 t. h9 R
    8( O1 T) O& D. Z5 N3 I
    98 O# b% @" T3 ?, A! v. b
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。9 B( v+ W6 S* S, O
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间8 ?1 D2 q% x* ?, C: L* }1 y4 b# f
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    ' L( q- S  b( \8 s0 v3 M  _5 Xs.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)+ h  e6 c; b+ H2 U
    - w$ Y# N6 F& ~4 L; v1 S& N1 ]; H
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    1 E/ M8 e; K5 k6 V1 `+ g- Q6 V1$ f( Q( ?2 Z. Q/ K
    2
    ; n- {! j# n  `2 \3+ l4 O/ I+ ?, e* d+ V: E! X8 H
    4
    ) G* I! B( R! b' ~: M5
    3 T6 @! l' m' |  J%pylab inline
    9 N2 H6 O: T/ h7 ~! ?) l_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    5 K' s+ o9 a; wplt.xlabel(' Timedelta'): V7 a/ b# g- R# r  d' ]. A6 N8 u0 R
    plt.title(" Timedelta of solar")
    ! \3 k  |% S. R, h  H1
    ( [  p) Q/ F# b+ u, i2
    # o1 ^* y# V  w0 w$ j  G3
    5 D/ g& h7 e* h0 m$ @3 S% \42 @8 ?) |1 n: Z$ ~4 @( d* h

    / h, z* ]; ~' J9 D, X4 ~4 b- k: o; h/ B4 t' _$ [4 d) x- l
    求如下指标对应的Series:' [$ g1 Z% p) G
    温度与辐射量的6小时滑动相关系数
    ) ^' S7 Y$ m6 h' C8 t以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列% R* @) L$ b" F+ K, g
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    0 @# E0 B/ {+ o$ x# F* `df.Radiation.rolling('6H').corr(df.Temperature).tail()
    , y# }0 w& w. r3 k9 n" l  |
    6 _$ U) h/ R( F" p! aData
    3 J! O, x9 X7 ^$ |' X2016-12-31 23:35:02    0.416187  t. k) U" n' z
    2016-12-31 23:40:01    0.416565  u0 c% J2 J  Q
    2016-12-31 23:45:04    0.328574
    9 j& u% L* H! T5 N* w. L7 c2016-12-31 23:50:03    0.261883- {$ \( p, a+ I, s& z( W
    2016-12-31 23:55:01    0.2624069 x9 Q' D0 g1 z* @) {9 r
    dtype: float64% I/ Y. i1 M4 y6 n
    1
    $ Y6 k* I" ^) H( q! h26 a: q1 @! o$ j7 A$ [9 J3 P" S% O
    31 I9 L$ j8 g3 s( g
    4
    + @- W  e0 j2 E) }# n. T$ d5. J* b4 k8 N. `/ x
    6
    . k: l7 M) R# A. T7
    ; q* @1 a) a6 V' O& r* n( S. {8/ Q+ h! Z2 r8 ^# ^/ A
    9# q# C6 U+ f, m2 ~. B" q( Z& U
    df['Temperature'].resample('6H',offset='3H').mean().head()- E9 `' w8 F5 h* E* Z: k/ j& A
    6 I% ]( T9 A, y' N4 c
    Data
    " Q& C) G) g, M. x2016-08-31 21:00:00    51.218750. R5 d! a* _/ ^' g
    2016-09-01 03:00:00    50.033333
    1 B0 g  |( v* @5 ~2016-09-01 09:00:00    59.379310
    ; ^. a! ]- |+ F4 W7 E# i  k4 F* ^2016-09-01 15:00:00    57.984375$ F9 x0 G5 y, `+ b7 i
    2016-09-01 21:00:00    51.393939$ v5 I  Z# _* E/ Z5 y  I
    Freq: 6H, Name: Temperature, dtype: float64& v* _4 D- L* r
    1
    4 D  u, x3 m# E5 O/ r2. E9 U8 q% m. i2 a( f( ]
    3+ E2 n5 @9 J% V  Q
    4
    4 P0 _7 s9 l# E1 q4 B2 t2 \5/ ^8 f+ ]8 ?" _7 U; i
    6) Q% `% `0 H8 S
    7
    . i9 s# T* {' ^) {, {+ Q( W$ t89 b" O* p) r& {, X+ d
    9% K) M) }( P8 \: i; U, Y. J+ P
    最后一题参考答案:) ]/ B5 A+ a8 J
    0 ^( l  t- ]2 R) C/ Z
    # 非常慢
    & ^% ]& P0 s3 M2 vmy_dt = df.index.shift(freq='-6H')6 t1 I( y' t' M3 t6 N8 f% \
    int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]# H- `2 _3 ?  M0 X& t! J# g
    int_loc = np.array(int_loc).reshape(-1)
    4 z( n) q, u/ u& ^, Mres = df.Radiation.iloc[int_loc]9 X0 ]- d2 \, t: a, L
    res.index = df.index; Q, ^  v! s% b+ M; [4 _
    res.tail(3)
    ; G1 ~2 o: y0 p7 n. _/ K1( H( z! i9 ]+ d8 [& |& l! `
    2; P* d! z( [3 U0 S- E1 M
    3
    7 \1 D3 K0 S1 H: i7 x4* g8 {/ l8 a4 I1 q$ B% l& i7 E
    5$ }7 D$ R4 F; v6 i. e
    68 P8 F0 d& C' V5 `# D
    7* ^0 L7 P9 J% G: l
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级+ d8 J( U5 ?1 F- b. Q& Q' B3 Z5 K
    target = pd.DataFrame(
    1 q7 b7 m9 ?" x. x- |+ M    {
    - j2 F: E( x& B0 g0 @# s        "Time": df.index.shift(freq='-6H'),
    ( _1 J4 ?4 }4 L        "Datetime": df.index,
    , X( c3 W# W- D1 A5 a- P/ ^    }
    " m8 n+ R! X3 I" p) h6 T( `1 q)4 e0 t6 ~. }, r$ s# a7 Y, V3 g% j
    3 _  Z9 e5 }7 c
    res = pd.merge_asof(
    ( T- L4 r3 D! L8 g. c; ^: l    target,
    * R! t, f+ ^1 w  ?! h/ z% ~, t8 e: f# N% c    df.reset_index().rename(columns={"Datetime": "Time"}),( V! u- S- S) W$ B9 ~6 }
        left_on="Time",
    + M2 M7 H9 l8 ^9 m6 C    right_on="Time",3 u4 Z1 d" r; I6 n& U
        direction="nearest"/ ?- E3 A7 s0 M/ C5 W
    ).set_index("Datetime").Radiation) F, S( w6 l6 U1 e( G  R! J

    * U& X0 k) V5 Z6 {0 W4 vres.tail(3)2 D' f; ^0 R1 b' x% M2 {3 P2 m3 \
    Out[224]: & t1 P' F$ n( x3 K: y! n/ L
    Datetime( l4 j3 v! {  L) e0 L2 i
    2016-12-31 23:45:04    9.33
    7 |+ l- l( I- x8 I, a2016-12-31 23:50:03    8.49
      c' D; A7 e/ }% Y7 A3 j2016-12-31 23:55:01    5.84- V7 ]& P' y% o$ N; J7 U0 s
    Name: Radiation, dtype: float64! X3 D$ A6 V  q/ f* N% \

    7 G& e! L) \- \% U% i1) [5 m# @& D" O; a
    2  m' ~& l* D% q5 x6 P7 k' G! ^
    3) @2 r: I4 Q: H! B, l
    4
    1 \& R, Y0 h; f5
      M5 R5 s9 e/ T) ?6. F; _1 b( a/ z# X0 P
    7' h. U: q) [  {" A* }
    8
    5 H# p3 E; \7 d& ?7 U9
    - [0 [0 Y! J* y% X$ U10
    2 `* p# k8 [+ b- ^11
    0 ^6 A( |8 v8 E7 o- i12( v% q5 S3 i# d3 u
    13" b/ M+ ?$ ~- N* _. i. c
    14
    : p0 @, c) F* B) Y7 ~: M15
    8 V- e% v7 M* F, N0 h. l3 x$ W16* t0 f& d2 f. i; L7 }0 C8 M
    17
    1 C  S' B3 [3 e2 M) P4 |18& ]7 M1 ]1 O' T
    192 H# R) V) b0 y1 r
    203 U- k+ g7 p9 C  {% x
    21
    ; L1 Y' l0 n0 u5 i" j  Z4 C22
    ; l% C1 Z, |% n9 x) m; [) z' J23& b3 j# y6 `- p8 B
    Ex2:水果销量数据集' P9 }/ T3 G* \
    现有一份2019年每日水果销量记录表:# I& W+ Y& @, Z2 U

    . a: s/ I* n) d! Odf = pd.read_csv('../data/fruit.csv')0 y- u2 {3 I0 Q
    df.head(3)
    , R2 x8 g/ H6 c/ l* W! N5 E2 C2 i& m. Y9 Y" J: y. Q- j* [
    Out[131]: * N/ R  ?0 }0 Z7 r, j+ E
             Date  Fruit  Sale5 l5 a  k- k. Q0 E3 Q" m* b
    0  2019-04-18  Peach    157 p5 f7 _8 T& @/ L1 g+ s! {
    1  2019-12-29  Peach    15
    $ W/ P2 c1 \) U$ h6 x2  2019-06-05  Peach    19
    8 W$ P! f* r+ p! x1* S# |+ c$ Q& A/ {9 ~
    2
    7 C6 i% |9 N* S( `2 L4 [1 K3$ M$ D5 Q. ?; o- T; T5 B& X. v3 {
    4
    ' l3 H' l/ W& W$ b53 c+ V& D$ L6 `0 `, ^- N& g, J
    6
    8 \6 t% a1 T+ {8 B$ A72 Z) E4 g, ^) Z% ^' {! |4 V
    8& S9 \7 O+ @# k! P
    统计如下指标:  ~# V& X- B0 a1 W% s' `
    每月上半月(15号及之前)与下半月葡萄销量的比值
    9 c. i6 k  c' r( |9 U: u每月最后一天的生梨销量总和
    6 w1 o, \  F& j( S3 q9 _7 U每月最后一天工作日的生梨销量总和% |( e- h8 D1 j9 a. I; z7 Q5 \
    每月最后五天的苹果销量均值
    " I7 ?* B2 n" J8 a按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    ! s4 M$ a% T, _( Q% i6 b按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    6 ]" J1 u- V, ?3 _8 dimport numpy as np
    - ?: R4 g3 S# E' m# v# W% qimport pandas as pd
    # t4 o8 k" ]* X/ S* [% {4 @1 p1$ i5 ]9 u: M; {7 I& H
    2
    1 ~' e6 s" P5 T( X统计如下指标:
    : S9 q# ]5 m0 Q0 n每月上半月(15号及之前)与下半月葡萄销量的比值5 a7 a& p* X; h: y- W
    每月最后一天的生梨销量总和
      y" G- H1 k  _- @每月最后一天工作日的生梨销量总和
    4 I& f& Q; ~- v1 _, a. M3 B每月最后五天的苹果销量均值
    3 W: }: O/ Y; y; ^6 p5 l# 每月上半月(15号及之前)与下半月葡萄销量的比值
    8 g7 R% ~- {* u# v+ [* idf.Date=pd.to_datetime(df.Date)
    ) Y& N8 R4 x# U& Y/ F# asale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    ; n4 x! A, s9 L3 L; Z4 ?( ^sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    & J2 a0 Q  v* M& ~3 `7 C; \7 ]sale=pd.DataFrame(sale)" m9 k% l) r/ a/ `9 m) h" m
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},0 E4 Y) k$ T* i- y# z
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名/ [# G) ]1 x8 q8 O) a" N- ^
    sale.head() # 每个月上下半月的销量) {$ ]. z' C% H' ]! r
    ( H, n7 F- ~0 w2 x; Z% ^
      Month        15Days        Sale* l# v. @9 z5 O1 G+ L6 a) w8 x
    0        1        False        10503
    ; T  @# K2 @3 t1        1        True        12341% h+ K3 x$ E: h7 R
    2        2        False        10001
    . g8 B! `1 x' U1 F- a( p) V* K/ P3        2        True        10106
    4 L1 a6 H3 o  @. b! }4        3        False        128143 \3 t6 A9 c6 D* ^; S

    0 d: E3 k# G) \3 B9 y0 x# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序6 O( p" }& [  _2 \3 ?' U
    sale.groupby(sale['Month'])['Sale'].agg(
    - t5 O% ^! h. S                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())! p6 g9 |; ^/ y+ v' w

    ) c/ v4 H9 c, DMonth
    2 x+ f' S% U! ^% M* W9 E3 I" `# ]1     1.174998! s: T3 W3 ?3 }% }. B! T+ I# \- i
    2     1.010499$ n3 J, K# q: z: Y- Z' R
    3     0.7763381 p" {/ w( F- H
    4     1.026345
    ' b. C! l- Z3 t. b3 I5     0.900534: Z2 v, q0 R9 N/ B8 B
    6     0.980136
    . W4 \8 H" j0 |9 J) a& ]7     1.350960
    & X% ?, F- g) |. J7 o8     1.0915843 {) M7 v, n0 Y7 q, G7 Z3 z% s
    9     1.1165087 n3 a3 w. i  X: S9 Y) ]) ^0 r7 u
    10    1.020784# G$ W1 w0 v' s# c7 q: c5 ]
    11    1.275911! d) T1 T/ Q1 N+ B9 \5 v
    12    0.989662" e8 |8 a9 x0 G+ R, i, n
    Name: Sale, dtype: float64
    3 }6 L8 N# A9 s  s/ y' E2 \) g$ |5 g/ j
    1
    : l3 d  N! [4 b4 F2* f5 v8 Z7 c1 X0 E% ^7 u
    3# D0 Y3 W4 t) \2 o
    46 Z, j. t7 J% k! @/ K( ~
    5) ?; f5 l: K$ }8 w6 D4 ~! ^$ |
    6
    " {) f+ W: u8 O+ l9 ~7% w/ g4 C5 h3 ^/ p
    8
    ! U9 _4 Z2 [! J! K+ q9
    & a9 Z5 v4 T+ e  \. W10# J/ [- P5 _+ i8 p. a) q# [' V
    11+ @) M, Q9 ]. a( K6 _2 w
    12
    : a% n2 {1 d& Q. W13
    7 t0 q0 u. Z8 u1 M14
    , D/ O7 d: y- A3 y6 w8 r# ]3 y15
    2 |: H$ r, B) i- b5 S168 Y) ]2 d" c  W5 P9 B# S
    17
    ; _' k+ u, d) S! k9 n  [* e185 f! k1 _0 z+ H) L
    19
    : s, N* s/ I5 A9 ^: d/ T20& _9 T3 Z9 c9 A  D
    21
    1 e' k2 b! F) y) @; W$ z* I: p22! o# ?" k0 }3 ?! ^" a
    23
    + P$ @2 S3 \: }3 Q, Y245 N* z0 s& A) b! S
    25, h# i) T6 g! s. M4 l4 Q& X4 k
    26
    8 C# I$ Q0 Z" x. r8 h3 I- }$ X276 ]3 r4 q1 s! R  e
    280 ~3 O2 Z, w, ?3 B
    29: [! \% B) r8 _/ P- Z6 r+ X
    30
    2 S$ p) X* |2 p- k) u" q31& {! V- B4 B% }7 W: _0 z
    32
    . w. ]( Y9 q2 H0 F0 ^33
    8 E  c. f# W0 c! C* j1 j, {) n341 I: R5 O+ q. N7 z9 q' @
    # 每月最后一天的生梨销量总和
    4 x1 r) F2 O* k3 Y3 v+ |+ ndf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    4 S/ r, k* ~+ K: D4 A5 [! r5 K0 ^; O1 E
    Date) ?; M+ ~4 V/ d8 ^& ], t
    2019-01-31    847
    1 R! q. d7 f1 R7 j9 \6 ]+ P2019-02-28    774
    + J  o+ N( `; I+ @+ l3 @! o2019-03-31    761; a: F; R  B6 z* x4 ^* I
    2019-04-30    648  B' Z' W% O8 L- U
    2019-05-31    616
    # x; _" a; o4 n! x. }$ y1
    ' ~' V& P' p5 z) @4 n( O6 R! @, [2! t4 m- j3 m9 x/ g- i0 J" B& V
    35 e0 ^6 z+ t* D, Y( }2 S
    43 v( }" `( j4 K6 d; ]
    5
    - y$ G6 x# J1 I1 T) h62 r6 C6 _$ K5 [* M
    7
    0 s7 r/ S1 ]( S' k. @  N  w% a8" ?' o5 y3 z5 O: c
    90 I  y2 g. `; o
    # 每月最后一天工作日的生梨销量总和9 v+ q4 n6 k! k7 q; s1 M; D
    ls=df.Date+pd.offsets.BMonthEnd()+ j' m! _! ]/ k4 B: H& `
    my_filter=pd.to_datetime(ls.unique())  a, A: P: @5 ]$ f# N
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()8 _0 i' _$ Q% C' \
    1 D7 f) j; j( s' s0 e- e0 h, d
    Date% E4 E4 `5 k( x  P; [+ G5 \
    2019-01-31     847& y' q& m* g( \2 A2 K
    2019-02-28     774
    9 D6 u3 o$ C1 h+ z  z. T2019-03-29     510
    & W; U. i) A. m& ?+ x& L2019-04-30     648
    , j/ @. A6 C7 Y; T1 b2019-05-31     616
    4 Q4 R: _" X/ b1. P& i8 U; A: q+ I; W( ~* M
    2% f- v! Q' h0 b. H) T. L, Q% I
    30 ]1 A- W( U  A+ a6 t3 f4 y; Q
    4
    ! ~- G8 _! s4 H, Z) e. \3 F$ X! @5
    - K8 W# K  w# O# P7 |6. {9 M. f1 w: I1 y/ [5 y9 B: o
    72 A, X: r' `( c  f; O
    8
    ! ]0 b0 E/ B3 |! S  }$ ]9
    + P# @, h* h& Q, n' q8 m; D. g9 \10
    - }( \5 f8 n8 O. N: \+ ]! Q11' _0 S- P1 C. z' D" S" b
    # 每月最后五天的苹果销量均值
    3 V3 B$ W' T! Z1 B, |3 \0 l* c  Jstart, end = '2019-01-01', '2019-12-31', h8 b( e( p% M& D
    end = pd.date_range(start, end, freq='M')
    6 l% Y. b; _6 s# W3 l5 qend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    ) ~9 K& \7 b+ T* w" x% U. y+ e! Q7 t, [% d0 x% @& k
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    - A8 v' B! W% X; ~/ w% vtd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天# l4 o( O9 r. \& @- `
    end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表6 H8 ~; b* n% V" Q7 |
    9 a6 h/ t$ D# n9 w+ U5 Z
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量4 q/ A3 `: y4 @6 l0 K  p
    apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()& @  \3 \7 s. Q+ ?3 s4 r- ?7 Z

    ) H; D( C' o; I( h, {Date
    , Z2 K& b$ X) e1     65.313725
    9 L3 l/ o7 s8 n# Z% }1 i2     54.061538
    6 I5 G, K) L$ v9 p: X7 l2 H3     59.325581% J1 J2 |; `: m! t- G
    4     65.795455* p: c/ {- ^$ _0 a, e
    5     57.4651167 p+ C" h, M9 n4 X& d

    : ~# `- g9 S; \" T8 ~1 c5 e16 K  }7 s, s" c  y
    2
    0 S2 R* S3 l, R! u% ~6 n3, H6 `/ Q9 w! v7 A6 a; _
    43 Q4 `" d$ N0 V, p0 s( s4 w
    5$ e" l9 j& x9 g9 q
    6' d: A4 E% g, q1 P% B. N
    7) ]6 }% z4 ~# h: p( U
    8
    / |; m3 J* ^+ N. b9
    ; _, W4 G1 H: m7 j104 w3 _" }1 n' Q0 J
    11! v- f! O" F0 _6 B7 a
    12
    6 v  P: Z) o9 Y( ]( S8 }. R8 }4 A13( a" Q" U9 R& S. n( w
    14' {: P- P( e- B
    15
    ' R, P; U6 s" ]0 o. w16! L2 w0 ^9 d" T( Y0 d* y
    17- O& {  j: ]: J; a. T2 v
    18
    & ?/ o) C! F$ ]' n1 s8 A# 参考答案:
    ; S* s1 k2 z# D4 itarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    8 C* |" I  y; T+ e! p7 u  D            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)) o/ {' v# W; C' k$ {) ~
    8 Z; M/ m" C) Z9 J" J
    res = df.set_index('Date').loc[target_dt].reset_index(2 @" V5 N0 Q  P6 k9 b; M4 ?
                ).query("Fruit == 'Apple'")" G! s! e7 l( l: F
    $ g! c" S. h# y, U6 O  F) T5 }
    res = res.groupby(res.Date.dt.month)['Sale'].mean(4 F, ]: J  `8 b, y
                ).rename_axis('Month'), h7 h1 I+ j" s7 W) e

    % n! L$ o4 E2 j: B+ [9 b% C0 Q: S) ~9 d. d$ m+ j
    res.head()8 `6 y; E" }. A' n
    Out[236]:
    1 R0 r7 }7 r7 ZMonth
    9 C, H( R+ R9 Y7 f( p' w1    65.3137255 Q4 a. ]  p" d1 f
    2    54.061538* F$ L& P$ q$ }) }
    3    59.325581/ M/ b8 h) z- s3 p; ]
    4    65.795455
    + S) R& ^0 Y( C' {1 J" c5    57.465116
    5 M. G; p/ ?* n  b: KName: Sale, dtype: float64
    ( L0 w8 O' x2 _! j3 S8 c$ n* J- O4 V% R) b
    1: o. v: v% D6 J" O
    2
    7 M& u' o$ U- \3
    5 X: s- ]: V, \6 t8 f4" R6 i( @9 ^6 {0 x' A8 E: t
    59 }" B  K9 O2 z- @
    6: n6 k+ \$ }2 p9 V' h
    76 Y- [' w6 x# [8 H  q  X' o1 g
    8
    1 F. l2 S3 d" y& r' |& y( L2 P9$ h3 A6 A) K9 l) K( i8 ~
    10
    3 N% Z( Q9 _. L# d11
    $ G. Z; I/ w. x4 c0 B12+ |5 d! ]+ K8 P% |
    132 r# `; {: v! a$ z& @6 l
    14. D) h. d# G. Y. W
    15
    # ?+ N4 z/ V+ Y16
    $ E+ w  I) z- B2 D, ^# [4 |0 {17
    & F6 m% T: {& f, _3 P. L4 t( s18" x0 D% p6 H5 H9 Q4 q6 D
    194 _+ O  R+ I; I2 G, W
    20
    * G7 t- w" _8 \1 \% ]按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    4 h: B- W6 }4 K/ `. f0 d5 ^result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
    $ T& q6 \  d' P# k1 q                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 $ B$ }; C0 `0 x7 w( O4 p
                                            - d. }: j; u: J" Q# C  w! \
    result=result.unstack(1).rename_axis(index={'Date':'Month'},. X" a- p4 H3 \5 R
                     columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
      H9 z8 w5 f' C* y) g2 H' dresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)6 @$ j+ l! ?2 y& y
    result.head() # 索引名有空再改吧
    7 Y7 B/ _' E3 K; p( o. r5 H/ c6 ~6 K$ m* L0 t( C, v$ ?, y
              Week        0        1        2        3        4        5        6
    & F! ?9 \; V# S; s2 h2 q  JFruit Month                                                       
    7 T. _% U7 {2 p" D8 V" eApple        1        46        50        50        45        32        42        23% [' P' B6 P- n$ `8 {
    Banana        1        27        29        24        42        36        24        35% O# t$ _( ^+ D/ b( U% V# v
    Grape        1        42        75        53        63        36        57        46
    ( K( s. ~6 V. ~! i9 C) FPeach        1        67        78        73        88        59        49        72
    ; i5 N9 ^# c' X6 Z# CPear        1        39        69        51        54        48        36        40
    . f/ V* D  P$ j' D* d) E% {1$ @; X, L9 a/ A  I) h
    2
    ) p8 ?1 F  D3 H+ B30 i  C6 G  S" w8 ?% R9 S: q) |
    4" E0 d; ]( K8 r" J) e' X3 ?
    5
    3 L5 d6 `& W1 f- U63 y# z8 u( _' K. y+ I
    7
    - ^+ F; p! J4 h( i/ y81 }! u# I0 ^% t" {" k8 Q: e
    95 P+ t5 N1 f1 ~* b
    107 Q" F6 A1 M6 T# t
    11
    9 {4 f4 l; F6 h0 T2 X+ m' ?12" I6 |1 `# F6 m# e! y8 v
    138 q8 X# Y) O) L
    14) y0 ]: R3 U9 T3 f. r6 A
    150 S, N7 _& n8 J
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    - W! e3 l0 }, D: U# 工作日苹果销量按日期排序
    8 j9 G/ u+ J9 d( \. qselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()/ }/ c$ n1 x$ o& f( Q6 \4 Z& X
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
    8 R/ }! w4 \- j; [; N. v6 ]1 o, w$ c5 Zselect_bday.head()
    - T8 G/ n4 f# D, V. E, J& d6 |3 ^/ H4 H1 _3 P8 ~
    Date$ w5 N5 K. O- X8 m
    2019-01-01    189
    ; e2 c+ S1 i# p0 b3 e& u, C4 @2019-01-02    482
    ( s8 M! K5 l1 Q" f2019-01-03    890
    8 M' l$ G% t# C7 x2019-01-04    550! ]4 x" g+ D; F9 e$ s$ J
    2019-01-07    494
    1 G5 ?2 y5 Y/ o3 G/ N
    " B. \% s7 N& z# G, l0 }# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。) w* ^+ B- A. e3 d
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()- S2 u" K9 E8 C7 J, R! k

    8 |- o( ]* R7 Y' Q. |! a& w8 K4 }Date
    " g/ W% p( u, t3 Q; V2019-01-01    189.0000007 t9 J; r7 X- x0 |  l5 e) e  v
    2019-01-02    335.500000
    ; k% t/ M- D: \6 _" V$ [! s) S2019-01-03    520.333333
    4 M' P) U8 K& Z9 z, g3 x4 {2019-01-04    527.750000# A1 K  K, e7 g  s- ^# g: e' j
    2019-01-05    527.750000
      z" R& j1 S5 l% ^* }0 Y# `3 y+ h4 w5 v$ K: e6 |" \
    ————————————————
    # j+ a& q% i5 u版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    , C1 G2 u5 Y( _2 `: a原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    & }" M3 d( E, T4 d% t2 @- z+ B! A/ S  O3 Z

    ! l8 c1 f8 a' x8 g7 \
    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, 2025-8-10 10:47 , Processed in 0.473547 second(s), 50 queries .

    回顶部