產(chǎn)品經(jīng)理數(shù)據(jù)分析不求人(1) - 第一個Python程序
產(chǎn)品經(jīng)理數(shù)據(jù)分析不求人(2) - Pandas處理Excel
產(chǎn)品經(jīng)理數(shù)據(jù)分析不求人(3) - 拼團(tuán)購數(shù)據(jù)清洗
產(chǎn)品經(jīng)理數(shù)據(jù)分析不求人(4) - 拼團(tuán)購數(shù)據(jù)分析
在現(xiàn)實(shí)世界中,數(shù)據(jù)通常是復(fù)雜冗余,富有變化的,人工選取出來的特征依賴人力和專業(yè)知識,局限性太大。于是我們需要通過機(jī)器來學(xué)習(xí)和抽取特征,促進(jìn)特征工程的工作更加快速有效。前面幾篇文章講的都是如何通過人工選取關(guān)鍵因子來分析入?yún)⒑湍繕?biāo)之間的關(guān)聯(lián),其實(shí)效率是比較低的,效果也有很大提升空間。

所謂效率低是因?yàn)楫?dāng)入?yún)嫶蟮臅r候組合太多,無法一一窮舉,所謂效果還需提升因?yàn)槿斯みx擇的組合會有遺漏,而且很難判斷多個關(guān)鍵因子的權(quán)重,所以這事還是交給機(jī)器學(xué)習(xí)更合適,開始機(jī)器學(xué)習(xí)所面臨的第一個問題就是特征工程,哪些入?yún)⒖捎? 入?yún)⑷绾蜗蛄炕?,如何對特征進(jìn)行降維,如果標(biāo)注化,歸一化,對缺省值如何處理等等,都是這一篇文章希望探討的方向。
在機(jī)器學(xué)習(xí)和模式識別中,特征是在觀測現(xiàn)象中的一種獨(dú)立、可測量的屬性。選擇信息量大的、有差別性的、獨(dú)立的特征是模式識別、分類和回歸問題的關(guān)鍵一步。
最初的原始特征數(shù)據(jù)集可能太大,或者信息冗余,因此在機(jī)器學(xué)習(xí)的應(yīng)用中,一個初始步驟就是選擇特征的子集,或構(gòu)建一套新的特征集,減少功能來促進(jìn)算法的學(xué)習(xí),提高泛化能力和可解釋性。在表格數(shù)據(jù)中,觀測數(shù)據(jù)或?qū)嵗▽?yīng)表格的一行)由不同的變量或者屬性(表格的一列)構(gòu)成,這里屬性其實(shí)就是特征。但是與屬性一詞不同的是,特征是對于分析和解決問題有用、有意義的屬性。
為了更形象的表述,讓我們依然從一個實(shí)際案例出發(fā),現(xiàn)在我希望通過對用戶數(shù)據(jù)的分析,找到用戶的關(guān)鍵特征用以預(yù)測哪些用戶可能會轉(zhuǎn)化為付費(fèi)會員。那么我有哪些數(shù)據(jù)可以使用呢? 一方面是用戶畫像的基本屬性,如年齡,性別,會員等級,地區(qū)等,另一方面是用戶行為數(shù)據(jù),但是一個用戶有多條行為數(shù)據(jù),必須聚合成一維向量才能使用。
1. 數(shù)據(jù)采樣

原始數(shù)據(jù)非常龐大,針對需求場景如何采樣是需要仔細(xì)考慮的問題。
采樣時間: 正如人類大腦記憶符合艾賓浩斯遺忘曲線,數(shù)據(jù)也應(yīng)遵循同樣規(guī)律,年代太久遠(yuǎn)的數(shù)據(jù)價值會慢慢衰減,在數(shù)據(jù)量足夠大的情況下我們盡量拿比較新的數(shù)據(jù)。
采樣均衡: 如果完全隨機(jī)采樣的話,容易出現(xiàn)過擬或者欠擬的情況,盡量讓標(biāo)注分布的更平均一些,有幾類標(biāo)準(zhǔn)就讓數(shù)據(jù)幾等分。
業(yè)務(wù)邏輯: 已成為付費(fèi)會員的用戶,訂單量和購買金額必然上漲,我們要預(yù)測普通會員是否愿意加入付費(fèi)會員就必須要剔除掉成為
付費(fèi)會員后的行為數(shù)據(jù),所以應(yīng)該自成為付費(fèi)會員之日起向前推一段時間取數(shù)。
遵循以上邏輯先取了四十萬記錄出來。
2. 記錄打散

取數(shù)邏輯可能會造成記錄分布不均勻影響算法模型,最好先做一個攪亂的預(yù)處理。
下面的代碼從excel中讀取記錄,打亂順序后重新生成新的excel,四十萬條也花了幾分鐘才處理完畢。
df = pd.read_excel("d:/dev/svip_data_400k.xlsx")
df_shuffled = df.reindex(np.random.permutation(df.index))
df_shuffled.to_excel("d:/dev/svip_data_shuffled.xlsx")
3. 數(shù)據(jù)切分

因?yàn)閿?shù)據(jù)量過大調(diào)試費(fèi)時,先按行列切分一個子集出來,取11個參數(shù)(挑選比較有意義的字段)和1個標(biāo)注,10萬條記錄。
這11個參數(shù)中有部分是用戶基本屬性,還有一部分是從多條用戶行為數(shù)據(jù)中聚合出來的變量,如月均訂單數(shù),平均客單價,是否開通消費(fèi)貸,累計訂單數(shù)等都會對開通付費(fèi)會員的意愿產(chǎn)生影響。
df = pd.read_excel("d:/dev/svip_data_shuffled.xlsx")
# slice with specified column
df_sub = df[['sex', 'age', 'vmark_name', 'city_level', 'add_time', 'last_ord_dt', 'pcl_flag',
'brand_like_num', 'value_per_order', 'monthly_order_num', 'order_num', 'svip_status']]
# slice by row index
df_sub = df_sub.iloc[0:100000]
df_sub.to_excel("d:/dev/svip_data_100k.xlsx")
4. 數(shù)據(jù)標(biāo)準(zhǔn)化

4.1 連續(xù)特征離散化
業(yè)界很少直接將連續(xù)值作為邏輯回歸模型的特征輸入,而是將連續(xù)特征離散化為一系列分類特征交給邏輯回歸模型,主要原因是迭代速度更快,模型更穩(wěn)定和健壯,降低了模型過擬合的風(fēng)險。年齡,注冊時間,最近一單時間都屬于需要離散化的連續(xù)特征。年齡可以分成幾個年齡段,注冊時間可以轉(zhuǎn)化為一個月內(nèi)注冊,三個月內(nèi)注冊,半年內(nèi)注冊。。。離散化的規(guī)則就憑經(jīng)驗(yàn)和感覺了。
將年齡映射到四個年齡段,有些年齡值的變量類型不對,還有負(fù)值以及特別大的數(shù)字,也要先清理掉再分類。
def derive_age_range(age):
if ( (type(age)) != int or (age<0) or (age>80)):
return 'nan'
else:
value = ('20-', '20-29', '30-39', '40+')
range = (0, 20, 30, 40)
seq = list(filter(lambda x:x<=int(age), range))
return value[len(seq)-1]
以月為單位將注冊時間映射到距離現(xiàn)在的時間周期
def derive_reg_distance(reg_date):
today = datetime.datetime.now()
diff = (today.year - reg_date.year) * 12 + (today.month - reg_date.month)
value = (u'一個月內(nèi)', u'一到三個月', u'三到六個月', u'半年到一年', u'一年到兩年', u'兩年到三年', u'三年以上')
range = (0, 1, 3, 6, 12, 24, 36)
seq = list(filter(lambda x:x<diff, range))
return value[len(seq)-1]
以天為單位將最后訂單日期映射為距離現(xiàn)在的時間周期
def derive_ord_distance(ord_date):
today = datetime.datetime.now()
diff = (today - ord_date).days
value = (u'3天內(nèi)', u'3到7天', u'7到15天', u'15天到30天', u'30天到80天', u'80天到160天', u'160天以上')
range = (0, 3, 7, 15, 30, 80, 160)
seq = list(filter(lambda x:x<diff, range))
return value[len(seq)-1]
4.2 數(shù)值特征標(biāo)準(zhǔn)化
挑選了三個變量: 收藏品牌數(shù)量,月均訂單量,平均客單價,用數(shù)值建議column均值再除以column方差的方式標(biāo)準(zhǔn)化。
有少量的記錄數(shù)值為Null, 我手工處理了一下excel將Null替換為0.
以月均訂單數(shù)這個字段為例,我先寫段小代碼看看數(shù)值的散點(diǎn)分布情況
df = pd.read_excel("d:/dev/svip_data_100k.xlsx")
fig, ax = plt.subplots()
fig.set_size_inches(15,15)
plt.plot(np.arange(0,100000,1), df['monthly_order_num'].values,'ro')
plt.show()
大部分值聚集在5以內(nèi),少量值零星分布在10以上的區(qū)間里
把均值, 標(biāo)準(zhǔn)差,方差打印出來看看
print('mean:', df['monthly_order_num'].mean())
print('std:', df['monthly_order_num'].std())
print('var:', df['monthly_order_num'].var())
mean: 1.0306720000000242
std: 1.059033353550142
var: 1.12155164393166
用方差來實(shí)現(xiàn)標(biāo)準(zhǔn)化,重新生成散點(diǎn)圖
norm_mon = []
for index, row in df.iterrows():
monthly_order_num = row['monthly_order_num']
norm_mon.append((monthly_order_num - df['monthly_order_num'].mean()) / df['monthly_order_num'].var())
fig, ax = plt.subplots()
fig.set_size_inches(15,15)
plt.plot(np.arange(0,100000,1), norm_mon,'ro')
plt.show()
現(xiàn)在分布在10以上區(qū)間的落點(diǎn)數(shù)量減少,差異也變小了。
4.1和4.2中轉(zhuǎn)化的新字段插入到dataframe中,并將原有字段刪除。
4.3 分類特征數(shù)值化
會員等級就是一個典型的分類特征,從低到高分別有鐵牌,銅牌,銀牌,金牌,鉆石,皇冠。這種離散值需要用獨(dú)熱編碼(‘one-hot encoder‘)來處理, 使用二進(jìn)制數(shù)來表示每個變量的特征: 鐵牌即[1,0,0,0,0], 鉆石即[0,0,0,1,0] etc. 我們并沒有直接用1,2,3,4,5這幾個數(shù)字來表示5個等級,因?yàn)閿?shù)字的大小不應(yīng)具備實(shí)際意義對計算產(chǎn)生影響,它只是一個標(biāo)識而已。省份,城市,性別,包括前面已做離散化處理的變量都應(yīng)該用這種方式來處理,Python也提供了便利的函數(shù)將分類值轉(zhuǎn)化為獨(dú)熱碼。
def to_one_hot(df, columns, nan=True):
for column in columns:
# use pd.concat to join the new columns with original dataframe
df = pd.concat([df, pd.get_dummies(df[column], prefix=column, dummy_na=nan)], axis=1)
# drop the original columns
df.drop([column], axis=1, inplace=True)
return df
flat_columns_nan = ['sex', 'age_range']
flat_columns = ['vmark_name', 'city_level', 'reg_distance', 'last_ord_distance', 'pcl_flag', 'svip_status']
df_sub = to_one_hot(df_sub, flat_columns_nan)
df_sub = to_one_hot(df_sub, flat_columns, False)
經(jīng)過這樣的處理,原有的每個字段扁平化展開變?yōu)閿?shù)值矩陣
5. 數(shù)據(jù)保存
最后將數(shù)據(jù)序列化到HDFS格式的文件中保存,到這里數(shù)據(jù)準(zhǔn)備工作就完成了。
# write to binary db
df_sub.to_hdf('store.h5', 'w', format='table', complevel=3, complib='bzip2')
References: