Python之美——一隻數據狗的筆記[長期更新]

Python之美——一隻數據狗的筆記[長期更新]

來自專欄新交通人的技術閑談5 人贊了文章

兩年前咬咬牙跳了Matlab的坑,入手了Python,從此一發不可收的成了PY的重度依賴者。本人研究工作皆涉及大量的數據處理工作,PY和R作為數據分析的兩駕馬車,得其一者得天下。另外,我接觸的許多軟體皆比較小眾,每次在涉及二次開發時,很多都是Matlab之流不支持的,而PY又往往是官方指定介面。因此,PY作為程序界的黏合劑,實在是方便至極。

如今機器學習和深度學習之熱,再次炒熱了PY。當然,涉及到統計模型,R的功力還是更深的。很多前沿或者有一定深度的統計模型,在R中都能快速實現,但在PY中則沒有現成的package。因此,現在不得不承認,PY和R,各有千秋,要都熟稔才行。

寫此文,是為記錄一些靈感,供廣大PY愛好者,也供自己,學習與查閱。

List形式的for in if else

爬到一組房價數據,但經緯度皆以121.43247的string形式存儲於DataFrame的一列中,且對於空缺值,以int形式的0或者float行駛的0.00填充。也就是說,該列存在多種數據格式,必須寫條件判斷才能循環。現需要將其進行修正提取,將121.43247提取為121.43247,而對於空值,統一以int形式的0填充。

於是,最低級的寫法出現了:

for jj in range(0, len(all_fangjia)): if all_fangjia.loc[jj, len] > 3: all_fangjia.loc[jj, new_lat] = all_fangjia.loc[jj, lat].split()[1] all_fangjia.loc[jj, new_lon] = all_fangjia.loc[jj, lon].split()[1]

該法思路清晰,但速度奇慢。對該列數據進行遍歷,先判斷該數據長度,如果大於3,說明是string形式的,然後再按照進行拆分(需要用來轉義),選取第二個值進行提取。

思路是對的,但速度實在太慢了。於是,就要請出循環的list風格化了:

all_fangjia[new_lon] = [var.split()[1] if len(var) > 3 else 0 for var in all_fangjia[lon]]all_fangjia[new_lat] = [var.split()[1] if len(var) > 3 else 0 for var in all_fangjia[lat]]

將代碼壓縮至了兩行,速度更是提升了幾十上百倍(具體提升量級沒算,但反正速度是飛快的了)。此法非常關鍵,掌握了對之後的數據處理效率大有提升。

佛系空格分隔符的處理

在拿到某些奇葩的原始數據文件時,其不同列間的分隔不是傳統的,,而是奇葩的不規整的空格符,也就是說,某兩列用了三個空格符來分隔,某兩列則用了四個,甚至在一列中,某兩行用了2個空格分隔,某兩行則用了3個。。

對於這種佛系空格分隔符,一種處理方法就是用正則(re)表達式,而另一種非常簡單的方法,則是:

import pandas as pdtem=pd.read_csv(583211-2017.out, delim_whitespace=True, engine=python)

即在熊貓包裡面的read_csv中,設置delim_whitespace=True即可。

字元串數據轉化為數字編號

比如有N個樣本,且存在一列專門對其類別進行標記,但標記用的全是字元串,如「大」、「中」、「小」。為了之後處理方便,需要將其變成0、1、2這種數字形式。這時就需要請出category類型來操作了。相關操作皆針對DataFrame格式實現。

obj_df["body_style"] = obj_df[body_style"].astype(category)obj_df["body_style_cat"] = obj_df["body_style"].cat.codes

繪圖時批量改變所有字體大小

在利用matplotlib繪圖時,題目、坐標軸標籤、坐標軸名稱等等的字體大小都需要分別設置,非常麻煩,而下面的方法則可以批量一次性設置,修改起來也就隨之方便了。

應注意,如果有多個ax,則還需要再嵌套一層循環,先指向某一個ax.

import matplotlib.pyplot as pltfig,ax=plt.subplots()for item in ([ax.title, ax.xaxis.label, ax.yaxis.label] + ax.get_xticklabels() + ax.get_yticklabels()): item.set_fontsize(20)

批量快速導入Oracle

做數據工作的,拿Python去接資料庫是非常常見的事情,而Oracle又是資料庫裡面的老大哥。在此不介紹如何安裝介麵包cx_Oracle,只介紹如何快速將大量數據一次性導入到Oracle中。

在沒Get到此技能之前,我都是一條條的往裡面插入數據的,數據量小還好,一旦大起來,速度就奇慢無比了。

於是,便有了下面的思路:先打包,再導入:

#導入連接包import cx_Oracle as oracledb = oracle.connect(scott/redhat@192.168.223.138:1521/oracle.test)#對待導入的數據進行處理DFV = DFV.fillna(None)DFV = DFV.values.tolist()rows = []for jj in range(len(DFV)): # 轉list row = (DFV[jj][0], DFV[jj][1], DFV[jj][2], DFV[jj][3], DFV[jj][4], DFV[jj][5], DFV[jj][6]) rows.append(row)# 寫入數據cr = db.cursor()cr.prepare( insert into OTJ_WATERLINK_WK2 (linkid,fromnode,LONGITUDE,LATITUDE,GRIDID,ROADNAME,SECT) values (:1, :2, :3, :4, :5, :6, :7))cr.executemany(None, rows)db.commit()cr.close()

試過的都知道,速度杠杠的。再也不用擔心大型數據文件要花上好幾天才能擼進Oracle了。

Groupby不支持的函數如何使用

數據處理裡面的groupby簡直就是小白第一課也得學會的技能了。但groupby方便雖方便,很多時候卻不支持一些函數。比如,我要對某一列進行groupby,並對groupby後的數據塊內的另一列求分位數。這時:

train_day=data.groupby([TIMEID]).percentile()[GOSPEED]

卻顯示報錯,原因是groupby之後的數據塊不支持percentile()這個函數。

這時你想到的可能就是只能寫循環一步步進去了,不慌,groupby還給我們留了後路:

dg=data.groupby(TIMEID)for a,b in dg: z = np.percentile(b[GOSPEED],5)

不只是percentile(),其他什麼函數,都是可以這麼玩的。速度雖然比groupby慢了一些,但比直接寫循環進去要快不少。

指定區間,計算頻率

做頻率分布直方圖大家都會做,非常簡單,對離散型變數做頻數統計也很簡單,value_counts()函數就行,但如何對連續型變數按照指定的區間就行頻率統計呢?這裡就需要用到cut和value_counts()的結合了。

cut函數可以將一個區間進行切割,返回切割後的小塊,再將其作為參數傳遞給value_counts()函數,就可以得出結果了。

xse = range(1, 5000, 1)fanwei = list(range(0, 4500, 500))fenzu = pd.cut(xse, fanwei, right=False)print(fenzu.categories) # 分組區間,長度8pinshu = fenzu.value_counts() # series,區間-個數

讀入輸出文件的中文亂碼問題

這個問題大家幾乎都會遇到,解決方法也非常簡單,只要指定對了編碼,自然就不會亂碼了:

輸出CSV亂碼的話:

import codecsFGIS.to_csv(FINALPOINT.csv,encoding="utf_8_sig",index=False)

導入CSV亂碼的話:

test=pd.read_csv(busgps_0309_0310_71.txt,encoding=gbk) #gbk不行就改成『gb18030』

不論讀入導出啥文件,記住encoding不要亂,編碼就不會亂。

數據結構化輸出及讀取

某個變數需要先保存好,下次再來直接讀取,而不是重新計算?MATLAB裡面可以直接保存WORKPLACE裡面的變數,PY怎麼做呢?用pickle

import pickle#導出output = open(FWRON.pkl, wb)pickle.dump(FWRON, output, -1)output.close()#讀取pkl_file = open(FWRON.pkl, rb)FWRON = pickle.load(pkl_file)pkl_file.close()

多版本PY的管理

由於不同的包可能在不同版本下才能生存,所以一台電腦有好幾個PY很正常,而解決他們的共生問題也是十分的頭疼。比如我的電腦里就有三個版本的PY(我也不知道怎麼這麼多)。。其中,conda管理的兩個:2.7和3.4;還有在系統下的一個3.6。

對於用conda來管理的各種版本,則可以使用conda來進行切換,相對要簡單很多。切換完畢後,就可以在該版本下進行包的安裝管理。強烈建議用conda而非pip來安裝package。

conda info --envssource activate python34conda install -c conda-forge osmnxsudo pip install [package_name] --upgrade

而我之前沒用conda之前,一直都在用系統的3.6。所以,很多時候我還是要對3.6系統下的環境做配置。下面記錄了更新pip以及利用pip指定版本安裝包的過程。注意全程加上python3來指代PY3的版本(我默認是用的2.7),以及,記得加上--user,否則會一直報錯[Errno 13] Permission denied。

curl https://bootstrap.pypa.io/get-pip.py | python3 - --userpython3 -m pip install --user osmnx

PY版本是非常頭疼的事情。最好的辦法是完全基於conda來配置自己的環境。不要和我一樣,多個PY版本分散在各個地址,配置起來非常麻煩。

一行代碼解決兩個字元串組的匹配

近期在做特徵的時候,需要對異常站點進行清洗。其間遇到一個問題,記錄如下:

有一個list A,裡面存儲著系列表徵站點錯誤的關鍵詞,如「關閉」、「不開放」、「非運營」。

另外有一個list B,裡面存儲著所有站點的名稱,如「虹橋站」、「上海南站」、「五角場站」。

在list B中,有部分站點是出錯的,這些站點會在名稱中進行標記,如虹橋站出錯了,該站的名字會改成「(關閉)虹橋站」,當然,也可能改成「(不開放)虹橋站」。

現在需要把list B中所有的出錯站點找出來。

問題複述一遍就是:以list A中的每一個元素為關鍵詞,對list B中每一個元素進行匹配,如果list B中某個元素包含list A中的任意一個元素,則將list B中的該元素標記為FALSE。

當然寫循環,用 A in B,是肯定可以做的。但是,有沒有更簡潔的寫法呢?

嘗試了一下,是有的:

Wrong_list=[關閉,不開放,非運營]Test_list=[虹橋站,(不開放)虹橋站]Bool_result=[any(list(wrongs in var for wrongs in Wrong_list)) for var in Test_list]

最後返回:Bool_result=[FALSE, TRUE]

需要注意:

1)兩個for的順序:先寫for wrongs in Wrong_list,再寫for var in Test_list,最終得出的Bool_result才是針對Test_list的。

2)list在此的作用:將generator object 轉化為bool格式。

3)any在此的作用:表示「只要有一即可」。

4)括弧在此的作用:為any提供計算優先順序。

applymap與匿名函數

常常會遇到需要對矩陣中的所有數值執行某個函數的情況,但又懶得寫def,這時候就可以祭出applymap大殺器了:

DF.applymap(lambda x: -(x * math.log(x)) if x > 0 else np.nan)

這句話的功能是,對DF這個矩陣裡面的每一個大於0的值,執行-(x * math.log(x))的運算,如果該值小於0,則置為nan。

要注意applymap和apply的區別。後者是對行或列進行處理:

DF.apply(lambda x: sum(x != 0), axis=1)

如上面的代碼,則是返回每一行(axis=1)中不等於0的個數。

去除DF中含有重複名字的列

有時候MERGE多了,難免會出現一個DF裡面有好一些列完全一致——內容一致,列名也一致。這在某些時候,列名一致是容易出錯的,最好需要及時清理他們。清理方法是:

DF=DF.ix[:,~DF.columns.duplicated()]

一句話就可以去重啦,非常的利索有沒有。

選取groupby後某列最小值對應的行

做數據處理的時候常常會遇到這樣的問題:對於一個DF,我們按照A、B兩列進行groupby後,選取每個group內C列最小值所對應的行並返回。

DF1=DF.loc[DF.groupby([A,B])[C].idxmin()]

原理其實很簡單,用到了一個idxmin(),可以返回最小值對應的行索引。

根據列類型選取列

很多時候如果列很多,而且我們需要選取特定類型的列進行變化。比如,在做線性回歸時,把所有BOOL類型的列改為0,1類型:

Exposure_DATA_NEW.loc[:, Exposure_DATA_NEW.dtypes == np.bool] = Exposure_DATA_NEW.loc[:,Exposure_DATA_NEW.dtypes == np.bool].astype(int)

這裡用到了DF.dtypes == np.bool,來對列進行圈取。

對每一個group進行NA均值填充

很多時候我們在做缺失值填充時,會需要先groupby,然後再對每一個group,計算該group的均值,並填充至該group的缺失值處:

Exposure_DATA["surfacewid"] = Exposure_DATA.groupby("rank_artclass")["surfacewid"].transform( lambda x: x.fillna(x.mean()))

CX_ORACLE的中文亂碼問題

在利用CX_ORACLE讀入數據時,不做處理,中文就會直接跳問號。需要在程序前加上:

import osimport sysreload(sys)sys.setdefaultencoding("gbk")os.environ[NLS_LANG] = SIMPLIFIED CHINESE_CHINA.UTF8

去除列名,豎向疊加

npCombined_Net = np.concatenate([metro_route.as_matrix(), tem_null.as_matrix()], axis=0)metro_route = pd.DataFrame(npCombined_Net, columns=metro_route.columns)

Brew的安裝與運用

如何安裝:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

安裝完還找不到怎麼辦:

export PATH=/usr/local/bin:$PATH

利用brew來安裝一個mysql:

brew install mysql

矩陣的去方向groupby

在做出一個網路邊矩陣時,常常會出現這樣的情況,我們需要的是無向的,但邊矩陣卻是有向的。即,假如矩陣一共三類,『FROM』,『TO』,『VALUE』,我們認為FROM 3 TO 1和FROM 1 TO 3是一類的,因此,我們需要把FROM 3 TO 1和FROM 1 TO 3的VALUE 求均值。怎麼做:

edge_centrality_nodir = pd.DataFrame(np.sort(edge_centrality[[from, to]], axis=1), edge_centrality.index, edge_centrality[[from, to]].columns)edge_centrality_nodir[bc] = edge_centrality[bc]edge_centrality_nodir_f = edge_centrality_nodir.groupby([from, to]).mean()[bc].reset_index()

其中edge_centrality為有向矩陣,edge_centrality_nodir為矩陣無向化,edge_centrality_nodir_f為最終groupby後的結果。矩陣無向化的過程,實際是對每一行進行重新排序的過程。注意把from 和 to兩列單獨拎出來。

繪圖label只顯示兩位小數

from matplotlib.ticker import FormatStrFormatterfig, ax = plt.subplots()ax.yaxis.set_major_formatter(FormatStrFormatter(%.2f))

range()不能產生float??

不要慌,用arange:

import numpy as npnp.arange(1,10,0.1)

DF某一列中是否包含某個字元

比如要判斷DF的某一列中是否含有「A」這個字元:

DF[Names].str.contains(A)

那如果要把「A」這個字元替換成「-」怎麼辦呢:

DF[Names].str.replace(A,-)

真心的方便呀。比如你在處理時間欄位時,有些直接就成「XX年XX月XX日」這種格式了,這時你為了轉化為datetime,首先就是把「年」、「月」、「日」都替換成「-」。

多個DF的merge

譬如你有N個DF,這些DF具有相同的KEY列,你需要把他們按照這個KEY列一併MERGE起來。怎麼做?

首先把需要MERGE的放在dfs這個list裡面,然後用reduce來解決:

from functools import reducedfs = [ord_count, ord_real_mn, order_coup_mn, order_final_mn]df_final = reduce(lambda left, right: pd.merge(left, right, on=authid_s), dfs)

我們的宗旨是:代碼這東西,多寫一行都是在浪費生命。

list以及numpy的repeat

在構造全序列時,常常需要對一個list進行重複,重複又分為兩種:

  • [1,2,3]--->[1,1,2,2,3,3]
  • [1,2,3]--->[1,2,3,1,2,3]

需要注意,在python中這兩種寫法是截然不同的。假設我們需要構建三列,第一列為站點ID,第二列為每一天,第三列為每一個小時:

timerange = pd.DataFrame(pd.date_range(start=1/1/2017, end=12/31/2017, freq=D))timerange[0] = timerange[0].astype(str)full_time_range = pd.DataFrame({SHOPSEQ: np.repeat(list(shop_need_seq), 365 * 24), date: list(np.repeat(list(timerange[0]), 24)) * len(shop_need_seq), HOUR0: list(range(1, 25, 1)) * len(shop_need_seq) * 365})

DF中的mean和count是怎麼對待NAN的?

The internalcount()function will ignoreNaNvalues, and so willmean(). The only point where we getNaN, is when the only value isNaN. Then, we take the mean value of an empty set, which turns out to beNaN

即:默認情況下,DF的 count()和mean()函數都是自動忽視NAN的,在計算均值時,除非你的所有數都是NAN,才會出現NAN的結果。

reshape(-1)?-1是什麼size?

這是非常能提現python之懶的一個點,懶得什麼境界呢?就是你只知道變形後的列數,懶得算變形後的行數,你就拿-1代替好了。。:

假如是這麼一個array:

z = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

直接-1之後,變成12行1列的矩陣:

z.reshape(-1)array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])

我想把他變成2列,但我懶得算有幾行:在行數那裡寫-1,系統自動幫你補全為6行:

z.reshape(-1, 2)array([[ 1, 2], [ 3, 4], [ 5, 6], [ 7, 8], [ 9, 10], [11, 12]])

推薦閱讀:

頁面運行代碼 Jupyter Notebook
Python數據分析數據化運營:商品數據化運營概述與關鍵指標
老婆最近不開心,發消息老撤回,用Python破解了撤回的消息
在 Kubernetes 上運行一個 Python 應用程序
Python教程02-數字類型和運算符

TAG:Python | 數據分析 | 數據挖掘 |