關於前程無憂以‘資料分析’為關鍵詞的招聘資訊的資料分析
背景:作為個數據分析小菜鳥,深知知行合一的重要性。基於本人目前要在杭州尋找一份資料分析的初階工作的現實考量,故決定採用前程無憂上關鍵詞‘資料分析’和地點設定在杭州上的招聘資訊作為此次資料分析的資料來源來進行實操,同時也為了能讓自己更好的瞭解目前杭州關於資料分析崗位的招聘市場以及崗位的成長性作一個簡單的前瞻和展望。
方法:首先通過爬蟲的request和BeatifulSoup庫來進行所需資料的抓取(此次主要借鑑網上的原始碼進行適當修改爬取杭州的資料)。然後爬取的資料進行適當的清洗和整理,進行視覺化和分析操作,主要用到numpy,pandas以及matpotlib和wordcloud等python庫。最後對所獲的圖表進行合理的分析和適當的展望。此次分析採集資料時間為8月6日,樣本數為2372.
具體程式碼實現:
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt #首先還是我們的老三樣
df = pd.read_csv(r'/Users/herenyi/Downloads/前程無憂招聘資訊.csv', encoding = 'UTF-8') #讀取檔案
df.info() #快速瀏覽檔案
這就是大致資料全覽,資料型別各項標籤都為字串格式。不過這裡要提醒下,自己把列名換成英文比較好,不然等下引用列可能會出現不必要的麻煩。接下來就是選取我們想要的列,以及資料是否有重複值和缺失值了,我們繼續下一步。
df_norepeat = df.drop_duplicates(subset =[ '公司'], keep = 'first' )
df_norepeat.info() #我們通過drop_duplicates函式去除重複值,並且新建一列以便後面操作。並用info函式加以個數確認。
接下來我們處理缺失值,本次報告我們需要的主要資料方面的就是工資這列,所以我們需要處理工資這列的缺失值。而工資這列只有2325個值,相對於公司數字2338來說,是有缺失的,我們去掉沒有工資記錄的行。
df_clean = df_norepeat.dropna(subset = ['薪資']) #去掉沒有工資的空值,並傳遞到df_clean中。 df_clean.info()
這時候每個工資和公司都一一標定了,很舒服了。接下來我們要進行薪資裡的字串進行處理,這也是最複雜的部分,因為裡面有各種格式。
df_clean[‘薪資’] # 讓我們來看看薪資的構成。
我們可以看到,單位有以千/月,萬/月,萬/年主要這三種格式,我們需要把他們全部轉化為。由於還不怎麼熟悉正則表達,我們可以嘗試分割-來分隔最高和最低工資,以及如何區分單位的差別,我們需要定義個函式。
def cut_word(word, method): #傳入兩個引數,一個是資料集,一個是方法
position1 = word.find('年')
position2 = word.find('萬')
position3 = word.find('-') #定位到三個地方
if position1 != -1:
bottom = float(word[:position3]) * 10 / 12
top = float(word[position3 + 1:position2]) * 10 / 12 #首先如果找得到年,那單位即為為萬/年,此時的最低和最高薪水就可以轉化單位了。
elif position2 != -1 & position1 == -1:
bottom = float(word[:position3]) * 10
top = float(word[position3 + 1:position2]) * 10 #如果找不到年且找得到萬,那單位即為萬/月,計算最低最高薪水。
else:
bottom = float(word[:position3])
top = float(word[position3 + 1:position3 + 2]) #接下來就是千/月了,常規處理。
if method == 'bottom':
return bottom
else:
return top
df_clean['topsalary']= df_clean.薪資.apply(cut_word, method = 'top')
df_clean['bottomsalary']= df_clean.薪資.apply(cut_word, method = 'bottom') #求出最高和最低工資
然後我們發現這麼一個錯誤,貌似我們看到的前二十項裡面不包含這樣的格式,從擷取的片段來看260元應該是個實習生之類的日結崗位,我們需要再優化下我們的函式來刪除這些不規範的資料。
def cut_word(word, method): #傳入兩個引數,一個是資料集,一個是方法
position1 = word.find('年')
position2 = word.find('萬')
position3 = word.find('-')
position4 = word.find('千')
if position1 != -1:
bottom = float(word[:position3]) * 10 / 12
top = float(word[position3 + 1:position2]) * 10 / 12 #首先如果找得到年,那單位即為為萬/年,此時的最低和最高薪水就可以轉化單位了。
elif position2 != -1 & position1 == -1:
bottom = float(word[:position3]) * 10
top = float(word[position3 + 1:position2]) * 10 #如果找不到年且找得到萬,那單位即為萬/月,計算最低最高薪水。
elif position4 != -1:
bottom = float(word[:position3])
top = float(word[position3 + 1:position3 + 2]) #接下來就是千/月了,常規處理。
else:
import numpy as np
bottom = np.nan
top = np.nan #**我們想把不符合的資料全變為空值,以便後面刪除這些行**
if method == 'bottom':
return bottom
else:
return top
然後神奇的又來了,居然還有2萬以下/年的字元段,這就是不會正則精確匹配的壞處,但是土歸土,我們這樣查詢欄位也還是能解決的。我們再加個查詢就好了。
def cut_word(word, method): #傳入兩個引數,一個是資料集,一個是方法
position1 = word.find('年')
position2 = word.find('萬')
position3 = word.find('-')
position4 = word.find('千')
position5 = word.find('以') #定位到五個地方
if position1 != -1 & position5 == -1:
bottom = float(word[:position3]) * 10 / 12
top = float(word[position3 + 1:position2]) * 10 / 12 #首先如果找得到年,且找不到以下欄位,那單位即為為萬/年,此時的最低和最高薪水就可以轉化單位了。
elif position2 != -1 & position1 == -1:
bottom = float(word[:position3]) * 10
top = float(word[position3 + 1:position2]) * 10 #如果找不到年且找得到萬,那單位即為萬/月,計算最低最高薪水。
elif position4 != -1:
bottom = float(word[:position3])
top = float(word[position3 + 1:position3 + 2]) #接下來就是千/月了,常規處理。
else:
import numpy as np
bottom = np.nan
top = np.nan #**我們想把不符合的資料全變為空值,以便後面刪除這些行**
if method == 'bottom':
return bottom
else:
return top
居然還有10萬以上/月的資料,我真是***了,所以說處理髒資料這塊是真心最煩的。我覺得要精確匹配,不然接下來鬼知道還有什麼奇奇怪怪的資料在。
def cut_word(word, method): #傳入兩個引數,一個是資料集,一個是方法
position1 = word.find('萬/年')
position2 = word.find('萬/月')
position3 = word.find('-')
position4 = word.find('千/月')
if position1 != -1 :
bottom = float(word[:position3]) * 10 / 12
top = float(word[position3 + 1:position1]) * 10 / 12
elif position2 != -1:
bottom = float(word[:position3]) * 10
top = float(word[position3 + 1:position2]) * 10
elif position4 != -1:
bottom = float(word[:position3])
top = float(word[position3 + 1:position4])
else:
import numpy as np
bottom = np.nan
top = np.nan
if method == 'bottom':
return bottom
else:
return top
df_clean['topsalary']= df_clean.薪資.apply(cut_word, method = 'top')
df_clean['bottomsalary']= df_clean.薪資.apply(cut_word, method = 'bottom')
df_clean = df_clean.dropna(subset = ['topsalary']) #刪除我們之前的空值行
終於處理好這些資料了,也沒執行bug。繼續下一步,這裡引入新的知識點,匿名函式lambda。很多時候我們並不需要複雜地使用def定義函式,而用lamdba作為一次性函式。lambda x: ******* ,前面的lambda x:理解為輸入,後面的星號區域則是針對輸入的x進行運算。
df_clean[‘avgsalary’] = df_clean.apply(lambda x: (x.topsalary+x.bottomsalary)/2, axis = 1)
df_clean = df_clean.rename(columns={'所在地':'location', '公司型別':'type', '工作經驗':'exp', '學歷':'education', '公司規模': 'scale'}) #不想再不斷切換輸入法了,把列名替換下。
df_need = df_clean [['location', 'type', 'exp', 'education', 'scale', 'avgsalary']] #篩選出所需的列。
df_need.head() #至此資料部分清理基本結束,接下來進行視覺化操作。
現在的資料就很清爽整潔了。
df_need.location.value_counts() #對字串用value_counts方法去分類。
df_need.avgsalary.describe() #對數值列我們用describe方法看下大概。
貌似還有很多異地招聘,為了更加準確的獲得杭州市場的資料,我們決定刪除異地的資料。接下來開始畫圖。好激動有木有!可以參考本人的matplotlib實戰來照葫蘆畫瓢。
location = ['杭州',
'杭州-濱江區',
'杭州-西湖區',
'杭州-江乾區',
'杭州-餘杭區',
'杭州-拱墅區',
'杭州-下城區',
'杭州-蕭山區',
'杭州-上城區']
df_need = df_need.loc[df_clean['location'].isin(location)] #我們又一步精簡資料到只剩杭州地區。
plt.style.use('ggplot') #使用ggplot配色,可以使圖片美觀,當然還有很多其他風格,大家自行百度
fig, ax = plt.subplots()
ax.hist(df_need['avgsalary'], bins = 20) #分成15組
ax.set_xlim(0, 40) #把x軸刻度縮小點
資料大體呈現個單峰狀,其中大部分聚集在5k到10k區間,越往後逐漸下降,基本符合現實。接下來我們進行分組,進行細分分析。首先是單因素條件下的分析。
ax = df_need.boxplot(column = 'avgsalary', by = 'location', figsize = (9,7))
這時候我們發現x軸刻度標籤顯示錯誤了,這時候需要我們匯入中文字型。網上的方法有很多,這裡我們採用用FontProperties方法。
#匯入中文,我電腦裡面隨便找了個ttf檔案,貌似是堡壘之夜裡面自帶的中文字型,哈哈。
from matplotlib.font_manager import FontProperties
chinese = FontProperties(fname = r'/Users/Shared/Epic Games/Fortnite/Engine/Content/Slate/Fonts/DroidSansFallback.ttf')
ax = df_need.boxplot(column = 'avgsalary', by = 'location', figsize = (9,7))
for label in ax.get_xticklabels():
label.set_fontproperties(chinese)
ax.set_ylim(0,50)
ax.set_xlabel('區域', fontproperties=chinese)
ax.set_ylabel('平均工資(千/月)', fontproperties=chinese)
ax.set_title('不同行政區域之間平均工資的箱體圖', fontproperties=chinese)
具體的圖表分析請參見結尾的PDF報告,這邊主要介紹程式碼實現,以下同理。
ax = df_need.boxplot(column = 'avgsalary', by = 'exp', figsize = (9,7))
for label in ax.get_xticklabels():
label.set_fontproperties(chinese)
ax.set_ylim(0,50)
ax.set_xlabel('工作經驗', fontproperties=chinese)
ax.set_ylabel('平均工資(千/月)', fontproperties=chinese)
ax.set_title('不同工作經驗之間平均工資的箱體圖', fontproperties=chinese)
以及之後的公司規模,學歷背景以及企業性質對平均工資的影響,就直接放圖了。
上面就是單因素分析的過程了,接下來我們想多因素角度下分析對平均薪資的影響。主要用到groupby。
df_need.groupby('location').count() #等價於之前的value.counts(),不過這是全列在按區域分組的資料
df_need.groupby(['location','education']).mean()
我們看到當在groupby裡傳入一個列表示,得到一組層次化的Series,這時候已經有初步的多因素分析基礎了,我們以此可以進行資料視覺化操作。
ax = df_need.groupby('location').mean().plot.bar(figsize = (9,7))
for label in ax.get_xticklabels():
label.set_fontproperties(chinese)
label.set_rotation(30)
ax.set_xlabel('區域', fontproperties=chinese)
ax.set_ylabel('平均工資(千/月)', fontproperties=chinese)
ax.set_title('不同行政區域之間平均工資的條形圖', fontproperties=chinese)
ax = df_need.groupby(['education', 'exp']).avgsalary.mean().unstack().plot.bar(figsize = (14,6))
for lable in ax.get_xticklabels():
lable.set_fontproperties(chinese)
lable.set_rotation(360)
ax.legend(prop=chinese)
ax.set_xlabel('學歷', fontproperties=chinese)
ax.set_ylabel('平均工資(千/月)', fontproperties=chinese)
ax.set_title(u'不同教育背景下平均工資與工作經驗的關係', fontproperties=chinese)
這是多因素下的條形圖,這邊就舉個例子,大家可以把groupby中的引數調換下,形成新的因素分析。這裡自己有個一直困惑的小知識點,可能會影響圖形選擇,我這裡夾帶點私貨,就是條形圖和直方圖的區別,貌似網上是這樣說的(條形圖主要用於展示分類資料,而直方圖則主要用於展示資料型資料)。我們接下來用下直方圖來展示下資料型資料,也就是我們這裡的平均工資這列。
fig, ax = plt.subplots()
ax.hist(x = df_need[df_need.education == '本科'].avgsalary, bins = 20, normed = 1,
facecolor = 'blue', alpha = 0.5, range=(0, 40))
ax.hist(x = df_need[df_need.education == '大專'].avgsalary, bins = 20, normed = 1,
facecolor = 'red', alpha = 0.5, range=(0, 40)) #alpha為透明度,normed=1表示顯示百分比。
ax.legend(['本科', '大專'], prop=chinese) #修改圖例名稱
ax.set_xlabel('平均工資(千/月)', fontproperties=chinese)
ax.set_title('本科與大專之間各平均工資段百分比人數', fontproperties=chinese)
通過直方圖,我們可以很清晰的看到本科與大專間的各薪資段百分人數。說明直方圖適合顯示資料型資料,比箱線圖更加清晰明瞭。最後我想用分桶的方法劃分薪資來看下各行政區內的薪資段百分比。
bins = [0, 5, 10, 20, 100]
df_need['level'] = pd.cut(df_need['avgsalary'], bins=bins) #我們以此分組為初,中,高,頂級資料分析師。
bins = [0, 5, 10, 20, 100]
df_need['level'] = pd.cut(df_need['avgsalary'], bins=bins) #我們以此分組為初,中,高,頂級資料分析師。
df_level = df_need.groupby(['location', 'level']).avgsalary.count().unstack()
df_level_percent = df_level.apply(lambda x: x/x.sum(), axis = 1)
ax = df_level_percent.plot.bar(stacked = True, figsize = (14,6))
for label in ax.get_xticklabels():
label.set_fontproperties(chinese)
label.set_rotation(30)
legends = ['0-5k','5k-10k','10k-20k','30k-100k']
ax.legend(legends, loc = 'upper right')
ax.set_xlabel('行政區', fontproperties=chinese)
ax.set_title('不同行政區內各薪資段百分比組成', fontproperties=chinese)
大家可以自己換換引數試一下,可能會發現其他有趣的結論哦。接下來我們就做一下關鍵字詞雲吧,看看這些企業到底有什麼福利!
df_clean['labels'] = df_clean.標籤 #先把列名改為英文
df_clean['labels']
word = df_clean.labels.str.split('|')
df_word = word.dropna().apply(pd.value_counts)
clean_word = df_word.unstack().dropna().reset_index()
把資料格式處理成這樣,在進行詞雲操作。
from wordcloud import WordCloud
df_word_counts = clean_word.groupby('level_0').count()
df_word_counts.index = df_word_counts.index.str.replace("'", "")
wordcloud = WordCloud(font_path=r'/Users/Shared/Epic Games/Fortnite/Engine/Content/Slate/Fonts/DroidSansFallback.ttf',
width=900, height=400, background_color='white')
f, axs = plt.subplots(figsize=(15, 15))
wordcloud.fit_words(df_word_counts.level_1)
axs = plt.imshow(wordcloud)
plt.axis('off')
這次實戰主要側重一般的資料清理,以及多維和視覺化的建立,通過此次專案大概基本程式碼都過了一遍,並且提升了自身解決問題能力。關於圖表的分析請參見連結: https://pan.baidu.com/s/1hpjkO2_HwwlJt2utMf2ANw 提取碼: ihg6。由於是個人第一個專案,圖表的分析還很稚嫩,主要關注點還是在實戰上,分析的話要結合更多的數理知識比如各因素的比重距離算出來顯得更加專業點,後續慢慢完善。