那些功能逆天,却鲜为人知的pandas骚操作
文章來源:Python數據科學
作者:東哥
pandas有些功能很逆天,但卻鮮為人知,本篇給大家盤點一下。
一、ACCESSOR
pandas有一種功能非常強大的方法,它就是accessor,可以將它理解為一種屬性接口,通過它可以獲得額外的方法。其實這樣說還是很籠統,下面我們通過代碼和實例來理解一下。
>>>?pd.Series._accessors {'cat',?'str',?'dt'}對于Series數據結構使用_accessors方法,可以得到了3個對象:cat,str,dt。
.cat:用于分類數據(Categorical data)
.str:用于字符數據(String Object data)
.dt:用于時間數據(datetime-like data)
下面我們依次看一下這三個對象是如何使用的。
str對象的使用
Series數據類型:str字符串
#?定義一個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關于以上str對象的2個方法說明:
Series.str.upper:將Series中所有字符串變為大寫
Series.str.count:對Series中所有字符串的個數進行計數
其實不難發現,該用法的使用與Python中字符串的操作很相似。沒錯,在pandas中你一樣可以這樣簡單的操作,而不同的是你操作的是一整列的字符串數據。仍然基于以上數據集,再看它的另一個操作:
>>>?regex?=?(r'(?P<city>[A-Za-z?]+),?'??????#?一個或更多字母 ...??????????r'(?P<state>[A-Z]{2})?'????????#?兩個大寫字母 ...??????????r'(?P<zip>\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關于以上str對象的2個方法說明:
Series.str.replace:將Series中指定字符串替換
Series.str.extract:通過正則表達式提取字符串中的數據信息
這個用法就有點復雜了,因為很明顯看到,這是一個鏈式的用法。通過replace將?" . " 替換為"",即為空,緊接著又使用了3個正則表達式(分別對應city,state,zip)通過extract對數據進行了提取,并由原來的Series數據結構變為了DataFrame數據結構。
當然,除了以上用法外,常用的屬性和方法還有.rstrip,.contains,split等,我們通過下面代碼查看一下str屬性的完整列表:
>>>?[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']屬性有很多,對于具體的用法,如果感興趣可以自己進行摸索練習。
dt對象的使用
Series數據類型:datetime
因為數據需要datetime類型,所以下面使用pandas的date_range()生成了一組日期datetime演示如何進行dt對象操作。
>>>?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]以上關于dt的3種方法說明:
Series.dt.day_name():從日期判斷出所處星期數
Series.dt.quarter:從日期判斷所處季節
Series.dt.is_year_end:從日期判斷是否處在年底
其它方法也都是基于datetime的一些變換,并通過變換來查看具體微觀或者宏觀日期。
cat對象的使用
Series數據類型:Category
在說cat對象的使用前,先說一下Category這個數據類型,它的作用很強大。雖然我們沒有經常性的在內存中運行上g的數據,但是我們也總會遇到執行幾行代碼會等待很久的情況。使用Category數據的一個好處就是:可以很好的節省在時間和空間的消耗。下面我們通過幾個實例來學習一下。
>>>?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上面我們通過使用sys.getsizeof來顯示內存占用的情況,數字代表字節數。還有另一種計算內容占用的方法:memory_usage(),后面會使用。
現在我們將上面colors的不重復值映射為一組整數,然后再看一下占用的內存。
>>>?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注:對于以上的整數值映射也可以使用更簡單的pd.factorize()方法代替。
我們發現上面所占用的內存是使用object類型時的一半。其實,這種情況就類似于Category data類型內部的原理。
內存占用區別:Categorical所占用的內存與Categorical分類的數量和數據的長度成正比,相反,object所占用的內存則是一個常數乘以數據的長度。
下面是object內存使用和category內存使用的情況對比。
>>>?colors.memory_usage(index=False,?deep=True) 650 >>>?colors.astype('category').memory_usage(index=False,?deep=True) 495上面結果是使用object和Category兩種情況下內存的占用情況。我們發現效果并沒有我們想象中的那么好。但是注意Category內存是成比例的,如果數據集的數據量很大,但不重復分類(unique)值很少的情況下,那么Category的內存占用可以節省達到10倍以上,比如下面數據量增大的情況:
>>>?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可以看到,在數據量增加10倍以后,使用Category所占內容節省了10倍以上。
除了占用內存節省外,另一個額外的好處是計算效率有了很大的提升。因為對于Category類型的Series,str字符的操作發生在.cat.categories的非重復值上,而并非原Series上的所有元素上。也就是說對于每個非重復值都只做一次操作,然后再向與非重復值同類的值映射過去。
對于Category的數據類型,可以使用accessor的cat對象,以及相應的屬性和方法來操作Category數據。
>>>?ccolors?=?colors.astype('category') >>>?ccolors.cat.categories Index(['burnt?orange',?'mint?green',?'navy',?'periwinkle',?'rose'],?dtype='object')實際上,對于開始的整數類型映射,可以先通過reorder_categories進行重新排序,然后再使用cat.codes來實現對整數的映射,來達到同樣的效果。
>>>?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:?int8dtype類型是Numpy的int8(-127~128)。可以看出以上只需要一個單字節就可以在內存中包含所有的值。我們開始的做法默認使用了int64類型,然而通過pandas的使用可以很智能的將Category數據類型變為最小的類型。
讓我們來看一下cat還有什么其它的屬性和方法可以使用。下面cat的這些屬性基本都是關于查看和操作Category數據類型的。
>>>?[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']但是Category數據的使用不是很靈活。例如,插入一個之前沒有的值,首先需要將這個值添加到.categories的容器中,然后再添加值。
>>>?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'??如果你想設置值或重塑數據,而非進行新的運算操作,那么Category類型不是那么有用。
二、從clipboard剪切板載入數據
當我們的數據存在excel表里,或者其它的IDE編輯器中的時候,我們想要通過pandas載入數據。我們通常的做法是先保存再載入,其實這樣做起來十分繁瑣。一個簡單的方法就是使用pd.read_clipboard()?直接從電腦的剪切板緩存區中提取數據。
這樣我們就可以直接將結構數據轉變為DataFrame或者Series了。excel表中數據是這樣的:
在純文本文件中,比如txt文件,是這樣的:
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將上面excel或者txt中的數據選中然后復制,然后使用pandas的read_clipboard()即可完成到DataFrame的轉換。parse_dates參數設置為 "d",可以自動識別日期,并調整為xxxx-xx-xx的格式。
>>>?df?=?pd.read_clipboard(na_values=[None],?parse_dates=['d']) >>>?dfa?????????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三、將pandas對象轉換為“壓縮”格式
在pandas中,我們可以直接將objects打包成為 gzip, bz2, zip, or xz 等壓縮格式,而不必將沒壓縮的文件放在內存中然后進行轉化。來看一個例子如何使用:
>>>?abalone?=?pd.read_csv(url,?usecols=[0,?1,?2,?3,?4,?8],?names=cols)>>>?abalonesex??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導入文件,讀取并存為abalone(DataFrame結構)。當我們要存為壓縮的時候,簡單的使用?to_json()?即可輕松完成轉化過程。下面通過設置相應參數將abalone存為了.gz格式的壓縮文件。
abalone.to_json('df.json.gz',?orient='records',lines=True,?compression='gzip')如果我們想知道儲存壓縮文件的大小,可以通過內置模塊os.path,使用getsize方法來查看文件的字節數。下面是兩種格式儲存文件的大小對比。
>>>?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四、使用"測試模塊"制作偽數據
在pandas中,有一個測試模塊可以幫助我們生成半真實(偽數據),并進行測試,它就是util.testing。下面同我們通過一個簡單的例子看一下如何生成數據測試:
>>>?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上面簡單的使用了
makeTimeDataFrame?和?makeDataFrame?分別生成了一組時間數據和DataFrame的數據。但這只是其中的兩個用法,關于testing中的方法有大概30多個,如果你想全部了解,可以通過查看dir獲得:
>>>?[i?for?i?in?dir(tm)?if?i.startswith('make')] ['makeBoolIndex','makeCategoricalIndex','makeCustomDataframe','makeCustomIndex',#?...,'makeTimeSeries','makeTimedeltaIndex','makeUIntIndex','makeUnicodeIndex']五、從列項中創建DatetimeIndex
也許我們有的時候會遇到這樣的情形(為了說明這種情情況,我使用了product進行交叉迭代的創建了一組關于時間的數據):
>>>?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)) >>>?dfyear??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明顯看到,列項中有year,month,day,它們分別在各個列中,而并非是一個完整日期。那么如何從這些列中將它們組合在一起并設置為新的index呢?
通過to_datetime的使用,我們就可以直接將年月日組合為一個完整的日期,然后賦給索引。代碼如下:
>>>?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當然,你可以選擇將原有的年月日列移除,只保留data數據列,然后squeeze轉換為Series結構。
>>>?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]The End來和小伙伴們一起向上生長呀!掃描下方二維碼,添加小詹微信,可領取千元大禮包并申請加入 Python 學習交流群,群內僅供學術交流,日常互動,如果是想發推文、廣告、砍價小程序的敬請繞道!一定記得備注「交流學習」,我會盡快通過好友申請哦!????長按識別,添加微信(添加人數較多,請耐心等待)????長按識別,關注小詹(掃碼回復 1024 領取程序員大禮包)總結
以上是生活随笔為你收集整理的那些功能逆天,却鲜为人知的pandas骚操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 知乎上高赞的40个有趣回复,很精辟!
- 下一篇: 5个酷毙的Python神器工具