那些功能逆天,卻鮮為人知的pandas騷操作
點(diǎn)擊上方Python知識(shí)圈,選擇設(shè)為星標(biāo)
回復(fù)1024獲取Python資料

文章來(lái)源:Python數(shù)據(jù)科學(xué)
作者:東哥
閱讀文本大概需要 6?分鐘
點(diǎn)擊「閱讀原文」查看pk哥原創(chuàng)精品視頻。
pandas有些功能很逆天,但卻鮮為人知,本篇給大家盤(pán)點(diǎn)一下。
一、ACCESSOR
pandas有一種功能非常強(qiáng)大的方法,它就是accessor,可以將它理解為一種屬性接口,通過(guò)它可以獲得額外的方法。其實(shí)這樣說(shuō)還是很籠統(tǒng),下面我們通過(guò)代碼和實(shí)例來(lái)理解一下。
>>>?pd.Series._accessors
{'cat',?'str',?'dt'}
對(duì)于Series數(shù)據(jù)結(jié)構(gòu)使用_accessors方法,可以得到了3個(gè)對(duì)象:cat,str,dt。
.cat:用于分類(lèi)數(shù)據(jù)(Categorical data)
.str:用于字符數(shù)據(jù)(String Object data)
.dt:用于時(shí)間數(shù)據(jù)(datetime-like data)
下面我們依次看一下這三個(gè)對(duì)象是如何使用的。
str對(duì)象的使用
Series數(shù)據(jù)類(lèi)型:str字符串
#?定義一個(gè)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
關(guān)于以上str對(duì)象的2個(gè)方法說(shuō)明:
Series.str.upper:將Series中所有字符串變?yōu)榇髮?xiě)
Series.str.count:對(duì)Series中所有字符串的個(gè)數(shù)進(jìn)行計(jì)數(shù)
其實(shí)不難發(fā)現(xiàn),該用法的使用與Python中字符串的操作很相似。沒(méi)錯(cuò),在pandas中你一樣可以這樣簡(jiǎn)單的操作,而不同的是你操作的是一整列的字符串?dāng)?shù)據(jù)。仍然基于以上數(shù)據(jù)集,再看它的另一個(gè)操作:
>>>?regex?=?(r'(?P
...??????????r'(?P
...??????????r'(?P
...
>>>?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
關(guān)于以上str對(duì)象的2個(gè)方法說(shuō)明:
- Series.str.replace:將Series中指定字符串替換
- Series.str.extract:通過(guò)正則表達(dá)式提取字符串中的數(shù)據(jù)信息
這個(gè)用法就有點(diǎn)復(fù)雜了,因?yàn)楹苊黠@看到,這是一個(gè)鏈?zhǔn)降挠梅?。通過(guò)replace將?" . " 替換為"",即為空,緊接著又使用了3個(gè)正則表達(dá)式(分別對(duì)應(yīng)city,state,zip)通過(guò)extract對(duì)數(shù)據(jù)進(jìn)行了提取,并由原來(lái)的Series數(shù)據(jù)結(jié)構(gòu)變?yōu)榱薉ataFrame數(shù)據(jù)結(jié)構(gòu)。
當(dāng)然,除了以上用法外,常用的屬性和方法還有.rstrip,.contains,split等,我們通過(guò)下面代碼查看一下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']
屬性有很多,對(duì)于具體的用法,如果感興趣可以自己進(jìn)行摸索練習(xí)。
dt對(duì)象的使用
Series數(shù)據(jù)類(lèi)型:datetime
因?yàn)閿?shù)據(jù)需要datetime類(lèi)型,所以下面使用pandas的date_range()生成了一組日期datetime演示如何進(jìn)行dt對(duì)象操作。
>>>?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]
以上關(guān)于dt的3種方法說(shuō)明:
- Series.dt.day_name():從日期判斷出所處星期數(shù)
- Series.dt.quarter:從日期判斷所處季節(jié)
- Series.dt.is_year_end:從日期判斷是否處在年底
其它方法也都是基于datetime的一些變換,并通過(guò)變換來(lái)查看具體微觀或者宏觀日期。
cat對(duì)象的使用
Series數(shù)據(jù)類(lèi)型:Category
在說(shuō)cat對(duì)象的使用前,先說(shuō)一下Category這個(gè)數(shù)據(jù)類(lèi)型,它的作用很強(qiáng)大。雖然我們沒(méi)有經(jīng)常性的在內(nèi)存中運(yùn)行上g的數(shù)據(jù),但是我們也總會(huì)遇到執(zhí)行幾行代碼會(huì)等待很久的情況。使用Category數(shù)據(jù)的一個(gè)好處就是:可以很好的節(jié)省在時(shí)間和空間的消耗。下面我們通過(guò)幾個(gè)實(shí)例來(lái)學(xué)習(xí)一下。
>>>?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
上面我們通過(guò)使用sys.getsizeof來(lái)顯示內(nèi)存占用的情況,數(shù)字代表字節(jié)數(shù)。還有另一種計(jì)算內(nèi)容占用的方法:memory_usage(),后面會(huì)使用。
現(xiàn)在我們將上面colors的不重復(fù)值映射為一組整數(shù),然后再看一下占用的內(nèi)存。
>>>?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
注:對(duì)于以上的整數(shù)值映射也可以使用更簡(jiǎn)單的pd.factorize()方法代替。
我們發(fā)現(xiàn)上面所占用的內(nèi)存是使用object類(lèi)型時(shí)的一半。其實(shí),這種情況就類(lèi)似于Category data類(lèi)型內(nèi)部的原理。
內(nèi)存占用區(qū)別:Categorical所占用的內(nèi)存與Categorical分類(lèi)的數(shù)量和數(shù)據(jù)的長(zhǎng)度成正比,相反,object所占用的內(nèi)存則是一個(gè)常數(shù)乘以數(shù)據(jù)的長(zhǎng)度。
下面是object內(nèi)存使用和category內(nèi)存使用的情況對(duì)比。
>>>?colors.memory_usage(index=False,?deep=True)
650
>>>?colors.astype('category').memory_usage(index=False,?deep=True)
495
上面結(jié)果是使用object和Category兩種情況下內(nèi)存的占用情況。我們發(fā)現(xiàn)效果并沒(méi)有我們想象中的那么好。但是注意Category內(nèi)存是成比例的,如果數(shù)據(jù)集的數(shù)據(jù)量很大,但不重復(fù)分類(lèi)(unique)值很少的情況下,那么Category的內(nèi)存占用可以節(jié)省達(dá)到10倍以上,比如下面數(shù)據(jù)量增大的情況:
>>>?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
可以看到,在數(shù)據(jù)量增加10倍以后,使用Category所占內(nèi)容節(jié)省了10倍以上。
除了占用內(nèi)存節(jié)省外,另一個(gè)額外的好處是計(jì)算效率有了很大的提升。因?yàn)閷?duì)于Category類(lèi)型的Series,str字符的操作發(fā)生在.cat.categories的非重復(fù)值上,而并非原Series上的所有元素上。也就是說(shuō)對(duì)于每個(gè)非重復(fù)值都只做一次操作,然后再向與非重復(fù)值同類(lèi)的值映射過(guò)去。
對(duì)于Category的數(shù)據(jù)類(lèi)型,可以使用accessor的cat對(duì)象,以及相應(yīng)的屬性和方法來(lái)操作Category數(shù)據(jù)。
>>>?ccolors?=?colors.astype('category')
>>>?ccolors.cat.categories
Index(['burnt?orange',?'mint?green',?'navy',?'periwinkle',?'rose'],?dtype='object')
實(shí)際上,對(duì)于開(kāi)始的整數(shù)類(lèi)型映射,可以先通過(guò)reorder_categories進(jìn)行重新排序,然后再使用cat.codes來(lái)實(shí)現(xiàn)對(duì)整數(shù)的映射,來(lái)達(dá)到同樣的效果。
>>>?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
dtype類(lèi)型是Numpy的int8(-127~128)??梢钥闯鲆陨现恍枰粋€(gè)單字節(jié)就可以在內(nèi)存中包含所有的值。我們開(kāi)始的做法默認(rèn)使用了int64類(lèi)型,然而通過(guò)pandas的使用可以很智能的將Category數(shù)據(jù)類(lèi)型變?yōu)樽钚〉念?lèi)型。
讓我們來(lái)看一下cat還有什么其它的屬性和方法可以使用。下面cat的這些屬性基本都是關(guān)于查看和操作Category數(shù)據(jù)類(lèi)型的。
>>>?[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數(shù)據(jù)的使用不是很靈活。例如,插入一個(gè)之前沒(méi)有的值,首先需要將這個(gè)值添加到.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'??
如果你想設(shè)置值或重塑數(shù)據(jù),而非進(jìn)行新的運(yùn)算操作,那么Category類(lèi)型不是那么有用。
二、從clipboard剪切板載入數(shù)據(jù)
當(dāng)我們的數(shù)據(jù)存在excel表里,或者其它的IDE編輯器中的時(shí)候,我們想要通過(guò)pandas載入數(shù)據(jù)。我們通常的做法是先保存再載入,其實(shí)這樣做起來(lái)十分繁瑣。一個(gè)簡(jiǎn)單的方法就是使用pd.read_clipboard()?直接從電腦的剪切板緩存區(qū)中提取數(shù)據(jù)。
這樣我們就可以直接將結(jié)構(gòu)數(shù)據(jù)轉(zhuǎn)變?yōu)镈ataFrame或者Series了。excel表中數(shù)據(jù)是這樣的:

在純文本文件中,比如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中的數(shù)據(jù)選中然后復(fù)制,然后使用pandas的read_clipboard()即可完成到DataFrame的轉(zhuǎn)換。parse_dates參數(shù)設(shè)置為?"d",可以自動(dòng)識(shí)別日期,并調(diào)整為xxxx-xx-xx的格式。
>>>?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三、將pandas對(duì)象轉(zhuǎn)換為“壓縮”格式
在pandas中,我們可以直接將objects打包成為?gzip, bz2, zip, or xz?等壓縮格式,而不必將沒(méi)壓縮的文件放在內(nèi)存中然后進(jìn)行轉(zhuǎn)化。來(lái)看一個(gè)例子如何使用:
>>>?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導(dǎo)入文件,讀取并存為abalone(DataFrame結(jié)構(gòu))。當(dāng)我們要存為壓縮的時(shí)候,簡(jiǎn)單的使用?to_json()?即可輕松完成轉(zhuǎn)化過(guò)程。下面通過(guò)設(shè)置相應(yīng)參數(shù)將abalone存為了.gz格式的壓縮文件。
abalone.to_json('df.json.gz',?orient='records',
????????????????lines=True,?compression='gzip')如果我們想知道儲(chǔ)存壓縮文件的大小,可以通過(guò)內(nèi)置模塊os.path,使用getsize方法來(lái)查看文件的字節(jié)數(shù)。下面是兩種格式儲(chǔ)存文件的大小對(duì)比。
>>>?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四、使用"測(cè)試模塊"制作偽數(shù)據(jù)
在pandas中,有一個(gè)測(cè)試模塊可以幫助我們生成半真實(shí)(偽數(shù)據(jù)),并進(jìn)行測(cè)試,它就是util.testing。下面同我們通過(guò)一個(gè)簡(jiǎn)單的例子看一下如何生成數(shù)據(jù)測(cè)試:
>>>?import?pandas.util.testing?as?tm
>>>?tm.N,?tm.K?=?15,?3??#?默認(rèn)的行和列
>>>?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上面簡(jiǎn)單的使用了makeTimeDataFrame?和?makeDataFrame?分別生成了一組時(shí)間數(shù)據(jù)和DataFrame的數(shù)據(jù)。但這只是其中的兩個(gè)用法,關(guān)于testing中的方法有大概30多個(gè),如果你想全部了解,可以通過(guò)查看dir獲得:
>>>?[i?for?i?in?dir(tm)?if?i.startswith('make')]
['makeBoolIndex',
?'makeCategoricalIndex',
?'makeCustomDataframe',
?'makeCustomIndex',
?#?...,
?'makeTimeSeries',
?'makeTimedeltaIndex',
?'makeUIntIndex',
?'makeUnicodeIndex']五、從列項(xiàng)中創(chuàng)建DatetimeIndex
也許我們有的時(shí)候會(huì)遇到這樣的情形(為了說(shuō)明這種情情況,我使用了product進(jìn)行交叉迭代的創(chuàng)建了一組關(guān)于時(shí)間的數(shù)據(jù)):
>>>?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明顯看到,列項(xiàng)中有year,month,day,它們分別在各個(gè)列中,而并非是一個(gè)完整日期。那么如何從這些列中將它們組合在一起并設(shè)置為新的index呢?
通過(guò)to_datetime的使用,我們就可以直接將年月日組合為一個(gè)完整的日期,然后賦給索引。代碼如下:
>>>?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當(dāng)然,你可以選擇將原有的年月日列移除,只保留data數(shù)據(jù)列,然后squeeze轉(zhuǎn)換為Series結(jié)構(gòu)。
>>>?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]

公眾號(hào):Python知識(shí)圈(ID:PythonCircle)博客:www.pyzhishiquan.com知乎:Python知識(shí)圈微信視頻號(hào):菜鳥(niǎo)程序員 (分享有趣的編程技巧、Python技巧)bilibili:菜鳥(niǎo)程序員的日常(目前原創(chuàng)視頻:10,累計(jì)播放量:15萬(wàn))
一個(gè)學(xué)習(xí)Python的人,喜歡分享,喜歡搞事情!長(zhǎng)按下圖二維碼關(guān)注,和你一起領(lǐng)悟Python的魅力。
留言打卡 DAY 33
今日的留言話題是:分享下你對(duì)數(shù)據(jù)處理的的一些簡(jiǎn)單或者實(shí)用的方法?關(guān)于留言打卡的規(guī)則參考:留言打卡第二季? (點(diǎn)擊鏈接查看規(guī)則),請(qǐng)按照?昵稱(chēng)+天數(shù)(請(qǐng)以自己實(shí)際打卡的天數(shù)為準(zhǔn),如day1 or day2 or day3)+?留言?xún)?nèi)容(不少于15字)的方式留言。
Python知識(shí)圈公眾號(hào)的交流群已經(jīng)建立,群里可以領(lǐng)取 Python 和人工智能學(xué)習(xí)資料,大家可以一起學(xué)習(xí)交流,效率更高,如果是想發(fā)推文、廣告、砍價(jià)小程序的敬請(qǐng)繞道!一定記得備注「交流學(xué)習(xí)」,我會(huì)盡快通過(guò)好友申請(qǐng)哦!通過(guò)好友后私聊我「學(xué)習(xí)資料」或者「進(jìn)群」都可以。
掃碼添加,備注:交流學(xué)習(xí)
往期推薦0102
用Python計(jì)算出小姐姐的顏值數(shù),看看你的女神顏值多少
朕已閱

