招聘网站|数据分析|Python

一、报告背景:
爬取某招聘网站数据,通过数据分析,知晓各城市薪资状况。

二、报告目的:
通过数据完成一份Python的薪资分析报告。

三、数据来源:
数据:https://pan.baidu.com/s/1DNoRDu-7IJAnY6NP7Slphg 提取码:vqxw
编程平台:Jupyter notebook

四、代码解析:

# -*- UTF-8 -*-
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('ggplot')#绘图风格,纯粹好看

df=pd.read_csv(r'C:\Users\41174\Desktop\DataAnalyst.csv',encoding='gb2312')  
#其余参数:names=columns(索引名称), sep=','(数据以逗号划分),#skiprows=1(不读头行数据)

在pandas中,常用的载入函数是read_csv、read_excel和read_table,table可以读取txt。若是服务器相关的部署,则还会用到read_sql,直接访问数据库,但它必须配合mysql相关包。

read_csv的encoding读取csv格式编码gb2312,该编码常见于windows,如果报错,可以尝试utf-8。

print(df.head())
 #看头部数据用head(),看尾部数据tail(),默认数量5,看更多数据,()中输入数量。
招聘网站|数据分析|Python_第1张图片
print(df.info())
招聘网站|数据分析|Python_第2张图片

如图,样本总数量:6876,其中companyLabelList,businessZones,secondType,positionLables都存在为空的情况。公司id和职位id为数字,其他都是字符串。

print(len(df.positionId.unique()) ) #不重复样本数量:5031
df_duplicates=df.drop_duplicates(subset='positionId',keep='first').copy()
print(df_duplicates.head())

DataFrame.drop_duplicates(subset=None, keep='first', inplace=False)
参数:
subset: 以哪个列为去重基准。
keep: {‘first’, ‘last’, False}, 默认值 ‘first’。
first: 保留第一次出现的重复项。
last: 保留最后一次出现。
False: 删除所有重复项。
inplace:布尔值,默认为False,是否覆盖原数据。

#处理下图中红色标注的脏数据。
def cut_word(word,method):
    position=word.find('-')
    length=len(word)
    if position !=-1:
        bottomSalary=word[:position-1]
        topSalary=word[position+1:length-1]
    else:
#upper()把字母转化成大写。
        bottomSalary=word[:word.upper().find('K')]
        topSalary=bottomSalary
    if method =='bottom':
        return bottomSalary
    else:
        return topSalary
    #return bottomsalary,topsalary   

df_duplicates['topSalary']=df_duplicates.salary.apply(cut_word,method='top')

df_duplicates['bottomSalary']=df_duplicates.salary.apply(cut_word,method='bottom')
招聘网站|数据分析|Python_第3张图片
#把薪资转化成数据类型:int。
df_duplicates.bottomSalary=df_duplicates.bottomSalary.astype('int')
df_duplicates.topSalary=df_duplicates.topSalary.astype('int')

#求平均薪资。
#lambda 详见:https://blog.csdn.net/BIT_SKY/article/details/50781806
df_duplicates['avgSalary']=df_duplicates.apply(lambda x:(x.bottomSalary+x.topSalary)/2,axis=1) 
#axis是apply中的参数,axis=0表示将函数用在行,axis=1则是列。
招聘网站|数据分析|Python_第4张图片
df_clean=df_duplicates[['city','companyShortName','companySize','education','positionLables','workYear','avgSalary','positionName']].copy()
print(df_clean.head())
招聘网站|数据分析|Python_第5张图片
print(df_clean.city.value_counts())
招聘网站|数据分析|Python_第6张图片

职位数量前3名:北京、上海、深圳,北京2347,职位需求量大。

print(df_clean.describe())
招聘网站|数据分析|Python_第7张图片

如图,平均薪资:17K,中位数:15K,最高:75K。

# 用来正常显示中文标签。
plt.rcParams['font.sans-serif']=['SimHei']
df_clean.avgSalary.hist(bins=20)
plt.show()
招聘网站|数据分析|Python_第8张图片

如图,更直观反映薪资分布情况。

#groupby()分组,median()中位数,sort_values(ascending=False)排序(降序)。
df_clean.groupby(df_clean.city).avgSalary.median().sort_values(ascending=False)
招聘网站|数据分析|Python_第9张图片

如图,中位数从高到低分别是北京、 深圳、 杭州、 上海、 苏州、 武汉、 成都、 西安、 广州、 厦门、 长沙、 南京、 天津。

#转化成category格式,方便重新排序索引值,为了让箱体图中位数从高到低排列。
df_clean.city=df_clean.city.astype('category')
df_clean.city.cat.set_categories(['北京','深圳','杭州','上海','苏州','武汉','成都','西安','广州','厦门','长沙','南京','天津'],inplace=True)
df_clean.boxplot(column='avgSalary',by='city',figsize=(9,6))
plt.show()
#boxplot是调用箱线图函数,column选择箱线图的数值,by是选择分类变量,figsize是尺寸。

如图,薪资最高前3名:北京、深圳、杭州。因为箱体上边缘出现许多异常值,但下边缘没有异常值,所以不能使用平均值来衡量,需要使用中位数来衡量。


招聘网站|数据分析|Python_第10张图片

什么时候用平均值、中位数、众数?
以箱体图为例,最优先考虑平均值,若出现单侧异常值时,不能使用平均值,需要改用中位数,但若出现双侧异常值时,不能使用中位数,需要把箱体分成2部分,通过上部和下部的众数来衡量整体情况。此例,由于出现单侧异常值,没有出现两侧异常值,可以使用中位数来衡量。

print(df_clean.groupby(df_clean.city).avgSalary.mean().sort_values(ascending=False))
招聘网站|数据分析|Python_第11张图片

如图,平均值从高到低依次排列:北京、 深圳、 上海、 杭州、 苏州、 成都、 广州、 武汉、 厦门、 南京、 西安、 长沙、 天津,北京始终一枝独秀达到19K。

#此处上侧方法一致,详见上方解释。
df_clean.groupby(df_clean.education).avgSalary.median().sort_values(ascending=False)

df_clean.education=df_clean.education.astype('category')
df_clean.education.cat.set_categories(['博士','本科','硕士','不限','大专'],inplace=True)
ax=df_clean.boxplot(column='avgSalary',by='education',figsize=(9,6))
plt.show()

print(df_clean.groupby(df_clean.education).avgSalary.mean().sort_values(ascending=False))
招聘网站|数据分析|Python_第12张图片
招聘网站|数据分析|Python_第13张图片

如图1,本科中位数薪资高于硕士生,容易误以为本科薪资高于硕士生,但硕士生薪资范围明显宽于本科生,同时结合图2,可见硕士生的平均薪资水平远高于本科生,由此可知,学历越高,薪资越高,知识改变命运。

df_clean.groupby(df_clean.workYear).avgSalary.median().sort_values(ascending=False)

df_clean.workYear=df_clean.workYear.astype('category')
df_clean.workYear.cat.set_categories(['10年以上','5-10年','3-5年','1-3年','不限','1年以下','应届毕业生'],inplace=True)
df_clean.boxplot(column='avgSalary',by='workYear',figsize=(9,6))

print(df_clean.groupby(df_clean.workYear).avgSalary.mean().sort_values(ascending=False))
招聘网站|数据分析|Python_第14张图片

招聘网站|数据分析|Python_第15张图片

如图,薪资与工作年限有很大关系,但优秀员工薪资明显超越年限限制。

#现在想知道北京和深圳这两座城市,学历对薪资的影响。
df_sz_bj=df_clean[df_clean['city'].isin(['深圳','北京'])]
df_sz_bj.boxplot(column='avgSalary',by=['education','city'],figsize=[14,6])
plt.show()
#深圳博士生只有1人,稀缺物种!
招聘网站|数据分析|Python_第16张图片

如图,薪资与工作区域有很大关系,北京薪资不管什么学历都高于同等学历的薪资状况,深圳博士薪资高于北京,由于是个例或者为引进高端人才的特例,暂不考虑。

#unstack()详见:https://www.cnblogs.com/bambipai/p/7658311.html
print(df_clean.groupby(['city','education']).avgSalary.mean().unstack())
招聘网站|数据分析|Python_第17张图片

如图,在不同城市中,博士学历最高的薪资在深圳,硕士学历最高的薪资在杭州。北京综合薪资最好。这个分析结论有没有问题呢?不妨先看招聘人数。

print(df_clean.groupby(['city','education']).avgSalary.count().unstack())
招聘网站|数据分析|Python_第18张图片

如图,博士学历岗位6个,所谓的平均薪资,也只取决于公司开出的价码,波动性很强,毕竟这只是招聘薪资,不代表真实的博士在职薪资。

#这里使用了agg函数,同时传入count和mean方法,然后返回了不同公司的计数和平均值两个结果。所以前文的mean,count,其实都省略了agg。agg除了系统自带的几个函数,它也支持自定义函数。
print(df_clean.groupby('companyShortName').avgSalary.agg(['count','mean']).sort_values(by='count',ascending=False))

现在我们有一个新的问题,我想计算出不同城市,招聘数据分析师需求前5的公司,应该如何处理?agg虽然能返回计数也能排序,但它返回的是所有结果,前五还需要手工计算。能不能直接返回前五结果?当然可以,这里再次请出apply。

#自定义了函数topN,将传入的数据计数,并且从大到小返回前五的数据。然后以city聚合分组,因为求的是前5的公司,所以对companyShortName调用topN函数。

df_clean.groupby('companyShortName').avgSalary.agg(lambda x:max(x)-min(x))

def topN(df,n=5):
    counts=df.value_counts()
    return counts.sort_values(ascending=False)[:n]

print(df_clean.groupby('city').companyShortName.apply(topN))
招聘网站|数据分析|Python_第19张图片

如图,数据相关职位需求前3:互联网和金融为主。

print(df_clean.groupby('city').positionName.apply(topN))
招聘网站|数据分析|Python_第20张图片

如图,北京和深圳主要职位需求是:数据分析师、数据产品经理、大数据开发工程师。

df_clean.groupby(['city','education']).avgSalary.mean().unstack().plot.bar()
plt.show()
招聘网站|数据分析|Python_第21张图片
plt.hist(x=df_clean[df_clean.city=='深圳'].avgSalary,
         bins=15,
         normed=1,
         facecolor='blue',
         alpha=0.5)
plt.hist(x=df_clean[df_clean.city=='北京'].avgSalary,
         bins=15,
         normed=1,
         facecolor='red',
         alpha=0.5)
plt.show()
招聘网站|数据分析|Python_第22张图片

如图,将深圳和北京的薪资数据以直方图的形式进行对比。因为深圳和北京的分析师人数相差较远,所以无法直接对比,需要用normed参数转化为密度。设置alpha透明度,它比箱线图更直观。

bins=[0,3,5,10,15,20,30,100]
level=['0-3','3-5','5-10','10-15','15-20','20-30','30+']
df_clean['level']=pd.cut(df_clean['avgSalary'],bins=bins,labels=level)
print(df_clean[['education','level']])

招聘网站|数据分析|Python_第23张图片

cut的作用是分桶,它也是数据分析常用的一种方法,将不同数据划分出不同等级,也就是将数值型数据加工成分类数据。详见: https://blog.csdn.net/a787264137/article/details/78573436

df_level=df_clean.groupby(['city','level']).avgSalary.count().unstack()
df_level_prop=df_level.apply(lambda x:x/x.sum(),axis=1)
ax=df_level_prop.plot.bar(stacked=True,figsize=(14,6))
plt.legend(bbox_to_anchor=(1, 1))
plt.xticks(rotation=0) #标签水平放置
 #标签放置位置详见:https://blog.csdn.net/Poul_henry/article/details/82533569
招聘网站|数据分析|Python_第24张图片

用lambda转换百分比,然后作堆积百分比柱形图(matplotlib好像没有直接调用的函数)。这里可以较为清晰的看到不同等级在不同地区的薪资占比。它比箱线图和直方图的好处在于,通过人工划分,具备业务含义。0~3是实习生的价位,3~6是刚毕业没有基础的新人,整理数据那种,6~10是有一定基础的,以此类推。

print(df_clean.positionLables)
招聘网站|数据分析|Python_第25张图片

现在的目的是统计数据分析师的标签。它只是看上去干净的数据,元素中的[]是无意义的,它是字符串的一部分,和数组没有关系。

你可能会想到用replace这类函数。但是它并不能直接使用。df_clean.positionLables.replace会报错,为什么呢?因为df_clean.positionLables是Series,并不能直接套用replace。apply是一个好方法,但是比较麻烦,这里需要str方法。

print(df_clean.positionLables.str[1:-1])
招聘网站|数据分析|Python_第26张图片

str方法允许我们针对列中的元素,进行字符串相关的处理,这里的[1:-1]不再是DataFrame和Series的切片,而是对字符串截取,这里把[]都截取掉了。如果漏了str,就变成选取Series第二行至最后一行的数据,切记。

word=df_clean.positionLables.str[1:-1].str.replace(' ','')
print(word)
招聘网站|数据分析|Python_第27张图片

使用完str后,它返回的仍旧是Series,当我们想要再次用replace去除空格。还是需要添加str的。现在的数据已经干净不少。

positionLables本身有空值,所以要删除,不然容易报错。再次用str.split方法,把元素中的标签按「,」拆分成列表。

df_word=word.dropna().str.split(',').apply(pd.value_counts) 
print(df_word)
#dropna详见:https://blog.csdn.net/weixin_38168620/article/details/79596798
招聘网站|数据分析|Python_第28张图片

这里是重点,通过apply和value_counts函数统计标签数。因为各行元素已经转换成了列表,所以value_counts会逐行计算列表中的标签,apply的灵活性就在于此,它将value_counts应用在行上,最后将结果组成一张新表。

df_word_counts=df_word.unstack().dropna().reset_index().groupby('level_0').count()

from wordcloud import WordCloud
df_word_counts.index=df_word_counts.index.str.replace("'","")

wc=WordCloud(font_path=r'C:\Windows\Fonts\FZSTK.TTF',width=900,height=400,background_color='white')
fig,ax=plt.subplots(figsize=(20,15))
wc.fit_words(df_word_counts.level_1)
ax=plt.imshow(wc)
plt.axis('off')
plt.show()

五、完整代码

# -*- UTF-8 -*-
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('ggplot')#绘图风格,纯粹好看

df=pd.read_csv(r'C:\Users\41174\Desktop\DataAnalyst.csv',encoding='gb2312')
#其余参数:names=columns(索引名称), sep=','(数据以逗号划分),#skiprows=1(不读头行数据)

print(df.head())
 #看头部数据用head(),看尾部数据tail(),默认数量5,看更多数据,()中输入数量。

print(df.info())

print(len(df.positionId.unique()))  #不重复样本数量:5031
df_duplicates=df.drop_duplicates(subset='positionId',keep='first').copy()
print(df_duplicates.head())


#处理下图中红色标注的脏数据。
def cut_word(word,method):
    position=word.find('-')
    length=len(word)
    if position !=-1:
        bottomSalary=word[:position-1]
        topSalary=word[position+1:length-1]
    else:
#upper()把字母转化成大写。
        bottomSalary=word[:word.upper().find('K')]
        topSalary=bottomSalary
    if method =='bottom':
        return bottomSalary
    else:
        return topSalary
    #return bottomsalary,topsalary

df_duplicates['topSalary']=df_duplicates.salary.apply(cut_word,method='top')

df_duplicates['bottomSalary']=df_duplicates.salary.apply(cut_word,method='bottom')

#把薪资转化成数据类型:int。
df_duplicates.bottomSalary=df_duplicates.bottomSalary.astype('int')
df_duplicates.topSalary=df_duplicates.topSalary.astype('int')

#求平均薪资。
#lambda 详见:https://blog.csdn.net/BIT_SKY/article/details/50781806
df_duplicates['avgSalary']=df_duplicates.apply(lambda x:(x.bottomSalary+x.topSalary)/2,axis=1)
#axis是apply中的参数,axis=0表示将函数用在行,axis=1则是列。

df_clean=df_duplicates[['city','companyShortName','companySize','education','positionLables','workYear','avgSalary','positionName']].copy()
print(df_clean.head())

print(df_clean.city.value_counts())

print(df_clean.describe())

# 用来正常显示中文标签。
plt.rcParams['font.sans-serif']=['SimHei']
df_clean.avgSalary.hist(bins=20)
plt.show()

#groupby()分组,median()中位数,sort_values(ascending=False)排序(降序)。
df_clean.groupby(df_clean.city).avgSalary.median().sort_values(ascending=False)

#转化成category格式,方便重新排序索引值,为了让箱体图中位数从高到低排列。
df_clean.city=df_clean.city.astype('category')
df_clean.city.cat.set_categories(['北京','深圳','杭州','上海','苏州','武汉','成都','西安','广州','厦门','长沙','南京','天津'],inplace=True)
df_clean.boxplot(column='avgSalary',by='city',figsize=(9,6))
plt.show()
#boxplot是调用箱线图函数,column选择箱线图的数值,by是选择分类变量,figsize是尺寸。

print(df_clean.groupby(df_clean.city).avgSalary.mean().sort_values(ascending=False))

#此处上侧方法一致,详见上方解释。
df_clean.groupby(df_clean.education).avgSalary.median().sort_values(ascending=False)

df_clean.education=df_clean.education.astype('category')
df_clean.education.cat.set_categories(['博士','本科','硕士','不限','大专'],inplace=True)
ax=df_clean.boxplot(column='avgSalary',by='education',figsize=(9,6))
plt.show()

print(df_clean.groupby(df_clean.education).avgSalary.mean().sort_values(ascending=False))

df_clean.groupby(df_clean.workYear).avgSalary.median().sort_values(ascending=False)

df_clean.workYear=df_clean.workYear.astype('category')
df_clean.workYear.cat.set_categories(['10年以上','5-10年','3-5年','1-3年','不限','1年以下','应届毕业生'],inplace=True)
df_clean.boxplot(column='avgSalary',by='workYear',figsize=(9,6))

print(df_clean.groupby(df_clean.workYear).avgSalary.mean().sort_values(ascending=False))

#现在想知道北京和深圳这两座城市,学历对薪资的影响。
df_sz_bj=df_clean[df_clean['city'].isin(['深圳','北京'])]
df_sz_bj.boxplot(column='avgSalary',by=['education','city'],figsize=[14,6])
plt.show()
#深圳博士生只有1人,稀缺物种!

#unstack()详见:https://www.cnblogs.com/bambipai/p/7658311.html
print(df_clean.groupby(['city','education']).avgSalary.mean().unstack())

print(df_clean.groupby(['city','education']).avgSalary.count().unstack())

#这里使用了agg函数,同时传入count和mean方法,然后返回了不同公司的计数和平均值两个结果。所以前文的mean,count,其实都省略了agg。agg除了系统自带的几个函数,它也支持自定义函数。
print(df_clean.groupby('companyShortName').avgSalary.agg(['count','mean']).sort_values(by='count',ascending=False))

df_clean.groupby('companyShortName').avgSalary.agg(lambda x:max(x)-min(x))

def topN(df,n=5):
    counts=df.value_counts()
    return counts.sort_values(ascending=False)[:n]

print(df_clean.groupby('city').companyShortName.apply(topN))

print(df_clean.groupby('city').positionName.apply(topN))


df_clean.groupby(['city','education']).avgSalary.mean().unstack().plot.bar()
plt.show()

plt.hist(x=df_clean[df_clean.city=='深圳'].avgSalary,
         bins=15,
         normed=1,
         facecolor='blue',
         alpha=0.5)
plt.hist(x=df_clean[df_clean.city=='北京'].avgSalary,
         bins=15,
         normed=1,
         facecolor='red',
         alpha=0.5)
plt.show()

bins=[0,3,5,10,15,20,30,100]
level=['0-3','3-5','5-10','10-15','15-20','20-30','30+']
df_clean['level']=pd.cut(df_clean['avgSalary'],bins=bins,labels=level)
print(df_clean[['education','level']])

df_level=df_clean.groupby(['city','level']).avgSalary.count().unstack()
df_level_prop=df_level.apply(lambda x:x/x.sum(),axis=1)
ax=df_level_prop.plot.bar(stacked=True,figsize=(14,6))
plt.legend(bbox_to_anchor=(1, 1))
plt.xticks(rotation=0) #标签水平放置
 #标签放置位置详见:https://blog.csdn.net/Poul_henry/article/details/82533569

print(df_clean.positionLables)

print(df_clean.positionLables.str[1:-1])
word=df_clean.positionLables.str[1:-1].str.replace(' ','')
df_word=word.dropna().str.split(',').apply(pd.value_counts)

word=df_clean.positionLables.str[1:-1].str.replace(' ','')
print(word)

df_word_counts=df_word.unstack().dropna().reset_index().groupby('level_0').count()

from wordcloud import WordCloud
df_word_counts.index=df_word_counts.index.str.replace("'","")

wc=WordCloud(font_path=r'C:\Windows\Fonts\FZSTK.TTF',width=900,height=400,background_color='white')
fig,ax=plt.subplots(figsize=(20,15))
wc.fit_words(df_word_counts.level_1)
ax=plt.imshow(wc)
plt.axis('off')
plt.show()

六、备注
若有错误,还望指出,我会及时更新,谢谢!

你可能感兴趣的:(招聘网站|数据分析|Python)