那些功能“逆天”,卻鮮為人知的pandas騷操作(另附Python教程)

pandas有些功能很逆天,但卻鮮為人知,本篇給大家盤點一下。

那些功能“逆天”,卻鮮為人知的pandas騷操作(另附Python教程)

一、ACCESSOR


pandas有一種功能非常強大的方法,它就是accessor,可以將它理解為一種屬性接口,通過它可以獲得額外的方法。其實這樣說還是很籠統,下面我們通過代碼和實例來理解一下。


<code>>>> pd.Series._accessors
{'cat', 'str', 'dt'}/<code>

對於Series數據結構使用_accessors方法,可以得到了3個對象:cat,str,dt。

  • .cat:用於分類數據(Categorical data)
  • .str:用於字符數據(String Object data)
  • .dt:用於時間數據(datetime-like data)


下面我們依次看一下這三個對象是如何使用的。


str對象的使用


Series數據類型:str字符串


<code># 定義一個Series序列
>>> addr = pd.Series([
...     'Washington, D.C. 20003',
...     'Brooklyn, NY 11211-1755',
...     'Omaha, NE 68154',
...     'Pittsburgh, PA 15211'
... ]) 

>>> addr.str.upper()
0     WASHINGTON, D.C. 20003
1    BROOKLYN, NY 11211-1755
2            OMAHA, NE 68154
3       PITTSBURGH, PA 15211
dtype: object

>>> addr.str.count(r'\\d') 
0    5
1    9
2    5
3    5
dtype: int64/<code>


關於以上str對象的2個方法說明:

  • Series.str.upper:將Series中所有字符串變為大寫
  • Series.str.count:對Series中所有字符串的個數進行計數


其實不難發現,該用法的使用與Python中字符串的操作很相似。沒錯,在pandas中你一樣可以這樣簡單的操作,而不同的是你操作的是一整列的字符串數據。仍然基於以上數據集,再看它的另一個操作:


<code>>>> regex = (r'(?P<city>[A-Za-z ]+), '      # 一個或更多字母
...          r'(?P<state>[A-Z]{2}) '        # 兩個大寫字母
...          r'(?P\\d{5}(?:-\\d{4})?)')  # 可選的4個延伸數字
...
>>> addr.str.replace('.', '').str.extract(regex)
         city state         zip
0  Washington    DC       20003
1    Brooklyn    NY  11211-1755
2       Omaha    NE       68154
3  Pittsburgh    PA       15211
/<state>/<city>/<code>


關於以上str對象的2個方法說明:

  • Series.str.replace:將Series中指定字符串替換
  • Series.str.extract:通過正則表達式提取字符串中的數據信息


這個用法就有點複雜了,因為很明顯看到,這是一個鏈式的用法。通過replace將 " . " 替換為"",即為空

緊接著又使用了3個正則表達式(分別對應city,state,zip)通過extract對數據進行了提取並由原來的Series數據結構變為了DataFrame數據結構。


當然,除了以上用法外,常用的屬性和方法還有.rstrip,.contains,split等,我們通過下面代碼查看一下str屬性的完整列表:


<code>>>> [i for i in dir(pd.Series.str) if not i.startswith('_')]
['capitalize',
 'cat',
 'center',
 'contains',
 'count',
 'decode',
 'encode',
 'endswith',
 'extract',
 'extractall',
 'find',
 'findall',
 'get',
 'get_dummies',
 'index',
 'isalnum',
 'isalpha',
 'isdecimal',
 'isdigit',
 'islower',
 'isnumeric',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'len',
 'ljust',

 'lower',
 'lstrip',
 'match',
 'normalize',
 'pad',
 'partition',
 'repeat',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'slice',
 'slice_replace',
 'split',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'wrap',
 'zfill']/<code>


屬性有很多,對於具體的用法,如果感興趣可以自己進行摸索練習。


dt對象的使用


Series數據類型:datetime


因為數據需要datetime類型,所以下面使用pandas的date_range()生成了一組日期datetime演示如何進行dt對象操作。


<code>>>> daterng = pd.Series(pd.date_range('2017', periods=9, freq='Q'))
>>> daterng
0   2017-03-31
1   2017-06-30
2   2017-09-30
3   2017-12-31
4   2018-03-31
5   2018-06-30
6   2018-09-30
7   2018-12-31
8   2019-03-31
dtype: datetime64[ns]

>>>  daterng.dt.day_name()
0      Friday
1      Friday
2    Saturday
3      Sunday
4    Saturday
5    Saturday
6      Sunday
7      Monday
8      Sunday
dtype: object

>>> # 查看下半年
>>> daterng[daterng.dt.quarter > 2]
2   2017-09-30
3   2017-12-31
6   2018-09-30
7   2018-12-31
dtype: datetime64[ns]

>>> daterng[daterng.dt.is_year_end]
3   2017-12-31
7   2018-12-31
dtype: datetime64[ns]/<code>


以上關於dt的3種方法說明:

  • Series.dt.day_name():從日期判斷出所處星期數
  • Series.dt.quarter:從日期判斷所處季節
  • Series.dt.is_year_end:從日期判斷是否處在年底

其它方法也都是基於datetime的一些變換,並通過變換來查看具體微觀或者宏觀日期。


cat對象的使用


Series數據類型:Category


在說cat對象的使用前,先說一下Category這個數據類型,它的作用很強大。雖然我們沒有經常性的在內存中運行上g的數據,但是我們也總會遇到執行幾行代碼會等待很久的情況。使用Category數據的一個好處就是:可以很好的節省在時間和空間的消耗。下面我們通過幾個實例來學習一下。


<code>>>> colors = pd.Series([
...     'periwinkle',
...     'mint green',
...     'burnt orange',
...     'periwinkle',
...     'burnt orange',
...     'rose',
...     'rose',
...     'mint green',
...     'rose',
...     'navy'
... ])
...
>>> import sys
>>> colors.apply(sys.getsizeof)
0    59
1    59
2    61
3    59
4    61
5    53
6    53
7    59
8    53
9    53
dtype: int64/<code>


上面我們通過使用sys.getsizeof來顯示內存佔用的情況,數字代表字節數。還有另一種計算內容佔用的方法:memory_usage(),後面會使用。


現在我們將上面colors的不重複值映射為一組整數,然後再看一下佔用的內存。


<code>>>> mapper = {v: k for k, v in enumerate(colors.unique())}
>>> mapper
{'periwinkle': 0, 'mint green': 1, 'burnt orange': 2, 'rose': 3, 'navy': 4}

>>> as_int = colors.map(mapper)
>>> as_int
0    0
1    1
2    2
3    0
4    2
5    3
6    3
7    1
8    3
9    4
dtype: int64

>>> as_int.apply(sys.getsizeof)
0    24
1    28
2    28
3    24
4    28
5    28
6    28
7    28
8    28
9    28
dtype: int64/<code>


注:對於以上的整數值映射也可以使用更簡單的pd.factorize()方法代替。


我們發現上面所佔用的內存是使用object類型時的一半。其實,這種情況就類似於Category data類型內部的原理。


內存佔用區別:Categorical所佔用的內存與Categorical分類的數量和數據的長度成正比,相反,object所佔用的內存則是一個常數乘以數據的長度。


下面是object內存使用和category內存使用的情況對比。


<code>>>> colors.memory_usage(index=False, deep=True)
650
>>> colors.astype('category').memory_usage(index=False, deep=True)
495/<code>


上面結果是使用object和Category兩種情況下內存的佔用情況。我們發現效果並沒有我們想象中的那麼好。但是注意Category內存是成比例的,如果數據集的數據量很大,但不重複分類(unique)值很少的情況下,那麼Category的內存佔用可以節省達到10倍以上,比如下面數據量增大的情況:


<code>>>> manycolors = colors.repeat(10)
>>> len(manycolors) / manycolors.nunique() 
20.0

>>> manycolors.memory_usage(index=False, deep=True)

6500
>>> manycolors.astype('category').memory_usage(index=False, deep=True)
585/<code>


可以看到,在數據量增加10倍以後,使用Category所佔內容節省了10倍以上。


除了佔用內存節省外,另一個額外的好處是計算效率有了很大的提升。因為對於Category類型的Series,str字符的操作發生在.cat.categories的非重複值上,而並非原Series上的所有元素上。也就是說對於每個非重複值都只做一次操作,然後再向與非重複值同類的值映射過去。


對於Category的數據類型,可以使用accessor的cat對象,以及相應的屬性和方法來操作Category數據。


<code>>>> ccolors = colors.astype('category')
>>> ccolors.cat.categories
Index(['burnt orange', 'mint green', 'navy', 'periwinkle', 'rose'], dtype='object')/<code>


實際上,對於開始的整數類型映射,可以先通過reorder_categories進行重新排序,然後再使用cat.codes來實現對整數的映射,來達到同樣的效果。


<code>>>> ccolors.cat.reorder_categories(mapper).cat.codes
0    0
1    1
2    2
3    0
4    2
5    3
6    3
7    1
8    3
9    4
dtype: int8/<code>


dtype類型是Numpy的int8(-127~128)。可以看出以上只需要一個單字節就可以在內存中包含所有的值。我們開始的做法默認使用了int64類型,然而通過pandas的使用可以很智能的將Category數據類型變為最小的類型。


讓我們來看一下cat還有什麼其它的屬性和方法可以使用。下面cat的這些屬性基本都是關於查看和操作Category數據類型的。


<code>>>> [i for i in dir(ccolors.cat) if not i.startswith('_')]
['add_categories',
 'as_ordered',
 'as_unordered',
 'categories',
 'codes',
 'ordered',
 'remove_categories',

 'remove_unused_categories',
 'rename_categories',
 'reorder_categories',
 'set_categories']/<code>


但是Category數據的使用不是很靈活。例如,插入一個之前沒有的值,首先需要將這個值添加到.categories的容器中,然後再添加值。


<code>>>> ccolors.iloc[5] = 'a new color'
# ...
ValueError: Cannot setitem on a Categorical with a new category,
set the categories first

>>> ccolors = ccolors.cat.add_categories(['a new color'])
>>> ccolors.iloc[5] = 'a new color'  /<code>


如果你想設置值或重塑數據,而非進行新的運算操作,那麼Category類型不是那麼有用。


二、從clipboard剪切板載入數據


當我們的數據存在excel表裡,或者其它的IDE編輯器中的時候,我們想要通過pandas載入數據。我們通常的做法是先保存再載入,其實這樣做起來十分繁瑣。一個簡單的方法就是使用pd.read_clipboard() 直接從電腦的剪切板緩存區中提取數據。


這樣我們就可以直接將結構數據轉變為DataFrame或者Series了。excel表中數據是這樣的:


那些功能“逆天”,卻鮮為人知的pandas騷操作(另附Python教程)


在純文本文件中,比如txt文件,是這樣的:


<code>a   b           c       d
0   1           inf     1/1/00
2   7.389056099 N/A     5-Jan-13
4   54.59815003 nan     7/24/18
6   403.4287935 None    NaT/<code>


將上面excel或者txt中的數據選中然後複製,然後使用pandas的read_clipboard()即可完成到DataFrame的轉換。parse_dates參數設置為 "d",可以自動識別日期,並調整為xxxx-xx-xx的格式。


<code>>>> df = pd.read_clipboard(na_values=[None], parse_dates=['d'])
>>> df
   a         b    c          d
0  0    1.0000  inf 2000-01-01
1  2    7.3891  NaN 2013-01-05
2  4   54.5982  NaN 2018-07-24
3  6  403.4288  NaN        NaT

>>> df.dtypes
a             int64
b           float64
c           float64
d    datetime64[ns]
dtype: object/<code>


三、將pandas對象轉換為“壓縮”格式


在pandas中,我們可以直接將objects打包成為 gzip, bz2, zip, or xz 等壓縮格式,而不必將沒壓縮的文件放在內存中然後進行轉化。來看一個例子如何使用:


<code>>>> abalone = pd.read_csv(url, usecols=[0, 1, 2, 3, 4, 8], names=cols)

>>> abalone
     sex  length   diam  height  weight  rings
0      M   0.455  0.365   0.095  0.5140     15
1      M   0.350  0.265   0.090  0.2255      7
2      F   0.530  0.420   0.135  0.6770      9
3      M   0.440  0.365   0.125  0.5160     10
4      I   0.330  0.255   0.080  0.2050      7
5      I   0.425  0.300   0.095  0.3515      8
6      F   0.530  0.415   0.150  0.7775     20
...   ..     ...    ...     ...     ...    ...
4170   M   0.550  0.430   0.130  0.8395     10
4171   M   0.560  0.430   0.155  0.8675      8
4172   F   0.565  0.450   0.165  0.8870     11
4173   M   0.590  0.440   0.135  0.9660     10
4174   M   0.600  0.475   0.205  1.1760      9
4175   F   0.625  0.485   0.150  1.0945     10
4176   M   0.710  0.555   0.195  1.9485     12/<code>


導入文件,讀取並存為abalone(DataFrame結構)。當我們要存為壓縮的時候,簡單的使用 to_json() 即可輕鬆完成轉化過程。下面通過設置相應參數將abalone存為了.gz格式的壓縮文件。


<code>abalone.to_json('df.json.gz', orient='records',
                lines=True, compression='gzip')/<code>


如果我們想知道儲存壓縮文件的大小,可以通過內置模塊os.path,使用getsize方法來查看文件的字節數。下面是兩種格式儲存文件的大小對比。


<code>>>> import os.path
>>> abalone.to_json('df.json', orient='records', lines=True)
>>> os.path.getsize('df.json') / os.path.getsize('df.json.gz')
11.603035760226396/<code>


四、使用"測試模塊"製作偽數據


在pandas中,有一個測試模塊可以幫助我們生成半真實(偽數據),並進行測試,它就是util.testing。下面同我們通過一個簡單的例子看一下如何生成數據測試:


<code>>>> import pandas.util.testing as tm
>>> tm.N, tm.K = 15, 3  # 默認的行和列

>>> import numpy as np

>>> np.random.seed(444)

>>> tm.makeTimeDataFrame(freq='M').head()
                 A       B       C
2000-01-31  0.3574 -0.8804  0.2669
2000-02-29  0.3775  0.1526 -0.4803
2000-03-31  1.3823  0.2503  0.3008
2000-04-30  1.1755  0.0785 -0.1791
2000-05-31 -0.9393 -0.9039  1.1837

>>> tm.makeDataFrame().head()
                 A       B       C
nTLGGTiRHF -0.6228  0.6459  0.1251
WPBRn9jtsR -0.3187 -0.8091  1.1501
7B3wWfvuDA -1.9872 -1.0795  0.2987
yJ0BTjehH1  0.8802  0.7403 -1.2154
0luaYUYvy1 -0.9320  1.2912 -0.2907/<code>


上面簡單的使用了

makeTimeDataFrame 和 makeDataFrame 分別生成了一組時間數據和DataFrame的數據。但這只是其中的兩個用法,關於testing中的方法有大概30多個,如果你想全部瞭解,可以通過查看dir獲得:


<code>>>> [i for i in dir(tm) if i.startswith('make')]
['makeBoolIndex',
 'makeCategoricalIndex',
 'makeCustomDataframe',
 'makeCustomIndex',
 # ...,
 'makeTimeSeries',
 'makeTimedeltaIndex',
 'makeUIntIndex',
 'makeUnicodeIndex']/<code>


五、從列項中創建DatetimeIndex


也許我們有的時候會遇到這樣的情形(為了說明這種情情況,我使用了product進行交叉迭代的創建了一組關於時間的數據):


<code>>>> from itertools import product
>>> datecols = ['year', 'month', 'day']

>>> df = pd.DataFrame(list(product([2017, 2016], [1, 2], [1, 2, 3])),
...                   columns=datecols)
>>> df['data'] = np.random.randn(len(df))
>>> df
    year  month  day    data
0   2017      1    1 -0.0767
1   2017      1    2 -1.2798
2   2017      1    3  0.4032
3   2017      2    1  1.2377
4   2017      2    2 -0.2060
5   2017      2    3  0.6187
6   2016      1    1  2.3786
7   2016      1    2 -0.4730
8   2016      1    3 -2.1505
9   2016      2    1 -0.6340
10  2016      2    2  0.7964
11  2016      2    3  0.0005/<code>


明顯看到,列項中有year,month,day,它們分別在各個列中,而並非是一個完整日期。那麼如何從這些列中將它們組合在一起並設置為新的index呢?


通過to_datetime的使用,我們就可以直接將年月日組合為一個完整的日期,然後賦給索引。代碼如下:


<code>>>> df.index = pd.to_datetime(df[datecols])
>>> df.head()
            year  month  day    data
2017-01-01  2017      1    1 -0.0767
2017-01-02  2017      1    2 -1.2798
2017-01-03  2017      1    3  0.4032
2017-02-01  2017      2    1  1.2377
2017-02-02  2017      2    2 -0.2060/<code>


當然,你可以選擇將原有的年月日列移除,只保留data數據列,然後squeeze轉換為Series結構。


<code>>>> df = df.drop(datecols, axis=1).squeeze()
>>> df.head()
2017-01-01   -0.0767
2017-01-02   -1.2798
2017-01-03    0.4032
2017-02-01    1.2377
2017-02-02   -0.2060
Name: data, dtype: float64

>>> df.index.dtype_str
'datetime64[ns]/<code>

最後小編為大家準備了Python學習的電子書籍及視頻教程

獲取方式:轉發此文,關注並私信小編“學習”即可免費領取哦

書籍部分截圖:

那些功能“逆天”,卻鮮為人知的pandas騷操作(另附Python教程)

視頻教程部分截圖:

那些功能“逆天”,卻鮮為人知的pandas騷操作(另附Python教程)


獲取方式:轉發此文,關注並私信小編“學習”即可免費領取哦


分享到:


相關文章: