作者:韓信子@ShowMeAI
教程地址:https://www.showmeai.tech/tutorials/84
本文地址:https://www.showmeai.tech/article-detail/178
聲明:版權所有,轉載請聯系平台與作者並注明出處
引言
文娛影音是目前大數據與AI應用最廣泛的場景之一,本案例以音樂專輯發行數據為背景,講解使用pyspark對HDFS存儲的數據進行處理數據分析的過程,並且對分析結果做了可視化呈現。
1.實驗環境
- (1)Linux: Ubuntu 16.04
- (2)Python: 3.8
- (3)Hadoop:3.1.3
- (4)Spark: 2.4.0
- (5)Web框架:flask 1.0.3
- (6)可視化工具:Echarts
- (7)開發工具:Visual Studio Code
為了支持Python可視化分析,大家可以運行如下命令安裝Flask組件:
sudo apt-get install python3-pip
pip3 install flask
2.實驗數據集
1)數據集說明
數據集和源代碼下載
鏈接:https://pan.baidu.com/s/1C0VI6w679izw1RENyGDXsw
提取碼:show
本案例的數據集來自於Kaggle平台,數據名稱albums.csv,包含了10萬條音樂專輯的數據(大家可以通過上述百度網盤地址下載)。主要字段說明如下:
- album_title:音樂專輯名稱
- genre:專輯類型
- year_of_pub: 專輯發行年份
- num_of_tracks: 每張專輯中單曲數量
- num_of_sales:專輯銷量
- rolling_stone_critic:滾石網站的評分
- mtv_critic:全球最大音樂電視網MTV的評分
- music_maniac_critic:音樂達人的評分
2)上傳數據至HDFS
(1)啟動Hadoop中的HDFS組件,在命令行運行下面命令:
/usr/local/hadoop/sbin/start-dfs.sh
(2)在hadoop上登錄用戶創建目錄,在命令行運行下面命令:
hdfs dfs -mkdir -p /user/hadoop
(3)把本地文件系統中的數據集albums.csv上傳到分布式文件系統HDFS中:
hdfs dfs -put albums.csv
3.pyspark數據分析
1)建立工程文件
(1)創建文件夾code
(2)在code下創建project.py文件
(3)在code下創建static文件夾,存放靜態文件
(4)在code/static文件夾下面創建data目錄,存放分析生成的json數據
2)進行數據分析
本文對音樂專輯數據集albums.csv進行了一系列的分析,包括:
(1)統計各類型專輯的數量
(2)統計各類型專輯的銷量總數
(3)統計近20年每年發行的專輯數量和單曲數量
(4)分析總銷量前五的專輯類型的各年份銷量
(5)分析總銷量前五的專輯類型,在不同評分體系中的平均評分
3)代碼實現
- 獲取數據集與代碼 → ShowMeAI的官方GitHub https://github.com/ShowMeAI-Hub/awesome-AI-cheatsheets
- 運行代碼段與學習 → 在線編程環境 http://blog.showmeai.tech/python3-compiler
project.py代碼如下:
from pyspark import SparkContext
from pyspark.sql import SparkSession
import json
#統計各類型專輯的數量(只顯示總數量大於2000的十種專輯類型)
def genre(sc, spark, df):
#按照genre字段統計每個類型的專輯總數,過濾出其中數量大於2000的記錄
#並取出10種類型用於顯示
j = df.groupBy('genre').count().filter('count > 2000').take(10)
#把list數據轉換成json字符串,並寫入到static/data目錄下的json文件中
f = open('static/data/genre.json', 'w')
f.write(json.dumps(j))
f.close()
#統計各個類型專輯的銷量總數
def genreSales(sc, spark, df):
j = df.select('genre', 'num_of_sales').rdd\
.map(lambda v: (v.genre, int(v.num_of_sales)))\
.reduceByKey(lambda x, y: x + y).collect()
f = open('static/data/genre-sales.json', 'w')
f.write(json.dumps(j))
f.close()
#統計每年發行的專輯數量和單曲數量
def yearTracksAndSales(sc, spark, df):
#把相同年份的專輯數和單曲數量相加,並按照年份排序
result = df.select('year_of_pub', 'num_of_tracks').rdd\
.map(lambda v: (int(v.year_of_pub), [int(v.num_of_tracks), 1]))\
.reduceByKey(lambda x, y: [x[0] + y[0], x[1] + y[1]])\
.sortByKey()\
.collect()
#為了方便可視化實現,將列表中的每一個字段分別存儲
ans = {}
ans['years'] = list(map(lambda v: v[0], result))
ans['tracks'] = list(map(lambda v: v[1][0], result))
ans['albums'] = list(map(lambda v: v[1][1], result))
f = open('static/data/year-tracks-and-sales.json', 'w')
f.write(json.dumps(ans))
f.close()
#取出總銷量排名前五的專輯類型
def GenreList(sc, spark, df):
genre_list = df.groupBy('genre').count()\
.orderBy('count',ascending = False).rdd.map(lambda v: v.genre).take(5)
return genre_list
#分析總銷量前五的類型的專輯各年份銷量
def GenreYearSales(sc, spark, df, genre_list):
#過濾出類型為總銷量前五的專輯,將相同類型、相同年份的專輯的銷量相加,並進行排序。
result = df.select('genre', 'year_of_pub', 'num_of_sales').rdd\
.filter(lambda v: v.genre in genre_list)\
.map(lambda v: ((v.genre, int(v.year_of_pub)), int(v.num_of_sales)))\
.reduceByKey(lambda x, y: x + y)\
.sortByKey().collect()
#為了方便可視化數據提取,將數據存儲為適配可視化的格式
result = list(map(lambda v: [v[0][0], v[0][1], v[1]], result))
ans = {}
for genre in genre_list:
ans[genre] = list(filter(lambda v: v[0] == genre, result))
f = open('static/data/genre-year-sales.json', 'w')
f.write(json.dumps(ans))
f.close()
#總銷量前五的專輯類型,在不同評分體系中的平均評分
def GenreCritic(sc, spark, df, genre_list):
#過濾出類型為總銷量前五的專輯,將同樣類型的專輯的滾石評分、mtv評分,音樂達人評分分別取平均
result = df.select('genre', 'rolling_stone_critic', 'mtv_critic', 'music_maniac_critic').rdd\
.filter(lambda v: v.genre in genre_list)\
.map(lambda v: (v.genre, (float(v.rolling_stone_critic), float(v.mtv_critic), float(v.music_maniac_critic), 1)))\
.reduceByKey(lambda x, y : (x[0] + y[0], x[1] + y[1], x[2] + y[2], x[3] + y[3]))\
.map(lambda v: (v[0], v[1][0]/v[1][3], v[1][1]/v[1][3], v[1][2]/v[1][3])).collect()
f = open('static/data/genre-critic.json', 'w')
f.write(json.dumps(result))
f.close()
#代碼入口
if __name__ == "__main__":
sc = SparkContext( 'local', 'test')
sc.setLogLevel("WARN")
spark = SparkSession.builder.getOrCreate()
file = "albums.csv"
df = spark.read.csv(file, header=True) #dataframe
genre_list = GenreList(sc, spark, df)
genre(sc, spark, df)
genreSales(sc, spark, df)
yearTracksAndSales(sc, spark, df)
GenreYearSales(sc, spark, df, genre_list)
GenreCritic(sc, spark, df, genre_list)
4)代碼運行
(1)在Ubuntu終端窗口中,用 hadoop 用戶登錄,在命令行運行 su hadoop
,並輸入用戶密碼。
(2)進入代碼所在目錄。
(3)為了能夠讀取HDFS中的 albums.csv
文件,在命令行運行:
/usr/local/hadoop/sbin/start-dfs.sh
(4)在命令行運行:
spark-submit project.py
4.可視化實現
=======
本案例的可視化基於Echarts實現,實現的可視化頁面部署在基於flask框架的web服務器上。
- 獲取數據集與代碼 → ShowMeAI的官方GitHub https://github.com/ShowMeAI-Hub/awesome-AI-cheatsheets
- 運行代碼段與學習 → 在線編程環境 http://blog.showmeai.tech/python3-compiler
1)相關代碼結構
(1)在code
目錄下新建 VisualizationFlask.py
文件,存放 Flask
應用。
(2)在code
目錄下新建一個名為 templates
的文件夾,存放 html
文件。
(3)在 code/static
目錄下新建一個名為 js
的文件夾,存放 js
文件。
2)建立Flask應用
在SparkFlask.py文件中復制以下代碼:
from flask import render_template
from flask import Flask
# from livereload import Server
app = Flask(__name__)
@app.route('/')
def index():
#使用 render_template() 方法來渲染模板
return render_template('index.html')
@app.route('/<filename>')
def req_file(filename):
return render_template(filename)
if __name__ == '__main__':
app.DEBUG=True#代碼調試立即生效
app.jinja_env.auto_reload = True#模板調試立即生效
app.run()#用 run() 函數來讓應用運行在本地服務器上
3)下載js文件
(1)在網站上下載jQuery(https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js),將其另存為 jquery.min.js
文件,保存在 code/static/js
目錄下。
(2)在官網下載界面下載Echarts(https://echarts.apache.org/zh/download.html),將其另存 echarts-gl.min.js
文件,保存在 code/static/js
目錄下。
4)Echarts可視化
(1)在code/templates目錄下新建index.html文件。復制以下代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Music</title>
</head>
<body>
<h2>音樂專輯分析</h2>
<ul style="line-height: 2em">
<li><a href="genre.html">各類型專輯的數量統計圖</a></li>
<li><a href="genre-sales.html">各類型專輯的銷量統計圖</a></li>
<li><a href="year-tracks-and-sales.html">近20年每年發行的專輯數量和單曲數量統計圖</a></li>
<li><a href="genre-year-sales.html">總銷量前五的專輯類型的各年份銷量分析圖</a></li>
<li><a href="genre-critic.html">總銷量前五的專輯類型的評分分析圖</a></li>
</ul>
</body>
</html>
index.html為主頁面,顯示每一個統計分析圖所在頁面的鏈接。點擊任意一個鏈接,即可跳轉到相應頁面。
(2)在code/templates目錄下新建genre.html文件。復制以下代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ECharts</title>
<!-- 引入 echarts.js -->
<script src="static/js/echarts-gl.min.js"></script>
<script src="static/js/jquery.min.js"></script>
</head>
<body>
<!-- 為ECharts准備一個具備大小(寬高)的Dom -->
<a href="/">Return</a>
<br>
<br>
<div id="genre" style="width: 480px;height:500px;"></div>
<script type="text/javascript">
$.getJSON("static/data/genre.json", d => {
_data = d.map(v => ({
name: v[0],
value: v[1]
}))
// 基於准備好的dom,初始化echarts實例
var myChart = echarts.init(document.getElementById('genre'), 'light');
// 指定圖表的配置項和數據
option = {
title: {
text: '各類型專輯的數量統計圖',
subtext: '從圖中可以看出Indie類型的專輯數量最多。',
// x: 'center'
x: 'left'
},
tooltip: {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
x: 'center',
y: 'bottom',
data: d.map(v => v[0])
},
toolbox: {
show: true,
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
magicType: {
show: true,
type: ['pie', 'funnel']
},
restore: { show: true },
saveAsImage: { show: true }
}
},
calculable: true,
series: [
{
name: '半徑模式',
type: 'pie',
radius: [30, 180],
center: ['50%', '50%'],
roseType: 'radius',
label: {
normal: {
show: false
},
emphasis: {
show: true
}
},
lableLine: {
normal: {
show: false
},
emphasis: {
show: true
}
},
data: _data
}
]
};
// 使用剛指定的配置項和數據顯示圖表。
myChart.setOption(option);
})
</script>
</body>
</html>
這個通過讀取 code/static/data/genre.json
中的數據,畫出玫瑰圖,顯示各類型專輯的數量。
(3)在code/templates目錄下新建genre-sales.html文件。復制以下代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ECharts</title>
<!-- 引入 echarts.js -->
<script src="static/js/echarts-gl.min.js"></script>
<script src="static/js/jquery.min.js"></script>
</head>
<body>
<a href="/">Return</a>
<br>
<br>
<!-- 為ECharts准備一個具備大小(寬高)的Dom -->
<div id="genre-sales" style="width: 1000px;height:550px;"></div>
<script type="text/javascript">
$.getJSON("static/data/genre-sales.json", d => {
console.log(d);
// 基於准備好的dom,初始化echarts實例
var myChart = echarts.init(document.getElementById('genre-sales'), 'light');
var dataAxis = d.map(v => v[0]);
var data = d.map(v => parseInt(v[1])/1e6);
option = {
title: {
text: '各類型專輯的銷量統計圖',
subtext: '該圖統計了各個類型專輯的銷量和,從圖中可以看出 Indie 類型的專輯銷量最高,將近 47 億。Pop 類型的專輯銷量排在第二,約為39億。',
x: 'center',
// bottom: 10
padding: [0, 0, 15, 0]
},
color: ['#3398DB'],
tooltip: {
trigger: 'axis',
axisPointer: { // 坐標軸指示器,坐標軸觸發有效
type: 'shadow' // 默認為直線,可選為:'line' | 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: dataAxis,
axisTick: {
show: true,
alignWithLabel: true,
interval: 0
},
axisLabel: {
interval: 0,
rotate: 45,
}
}
],
yAxis: [
{
type: 'value',
name: '# Million Albums',
nameLocation: 'middle',
nameGap: 50
}
],
series: [
{
name: '直接訪問',
type: 'bar',
barWidth: '60%',
data: data
}
]
};
// 使用剛指定的配置項和數據顯示圖表。
myChart.setOption(option);
})
</script>
</body>
</html>
這個通過讀取 code/static/data/genre-sales.json
中的數據,畫出柱狀圖,顯示各類型專輯的銷量總數。
(4)在code/templates目錄下新建year-tracks-and-sales.html文件。復制以下代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ECharts</title>
<!-- 引入 echarts.js -->
<script src="static/js/echarts-gl.min.js"></script>
<script src="static/js/jquery.min.js"></script>
</head>
<body>
<a href="/">Return</a>
<br>
<br>
<!-- 為ECharts准備一個具備大小(寬高)的Dom -->
<div id="canvas" style="width: 1000px;height:550px;"></div>
<script type="text/javascript">
$.getJSON("static/data/year-tracks-and-sales.json", d => {
console.log(d)
// 基於准備好的dom,初始化echarts實例
var myChart = echarts.init(document.getElementById('canvas'), 'light');
var colors = ['#5793f3', '#d14a61', '#675bba'];
option = {
title: {
text: '近20年的專輯數量和單曲數量的變化趨勢',
padding: [1, 0, 0, 15]
// subtext: '該圖顯示了從2000年到2019年發行的專輯數量和單曲數量的變化趨勢,從圖中可以看出,專輯數量變化很小,基本穩定在5000左右;單曲數量有輕微的波動,大概為專輯數量的10倍。'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['單曲數量', '專輯數量'],
padding: [2, 0, 0, 0]
},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: { readOnly: false },
magicType: { type: ['line', 'bar'] },
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: d['years'],
boundaryGap: ['20%', '20%']
},
yAxis: {
type: 'value',
// type: 'log',
axisLabel: {
formatter: '{value}'
}
},
series: [
{
name: '單曲數量',
type: 'bar',
data: d['tracks'],
barWidth: 15,
},
{
name: '專輯數量',
type: 'bar',
data: d['albums'],
barGap: '-100%',
barWidth: 15,
}
]
};
// 使用剛指定的配置項和數據顯示圖表。
myChart.setOption(option);
})
</script>
</body>
</html>
這個通過讀取 code/static/data/ year-tracks-and-sales.json
中的數據,畫出柱狀圖,顯示近20年每年發行的專輯數量和單曲數量。
(5)在code/templates目錄下新建genre-year-sales.html文件。復制以下代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ECharts</title>
<!-- 引入 echarts.js -->
<script src="static/js/echarts-gl.min.js"></script>
<script src="static/js/jquery.min.js"></script>
</head>
<body>
<a href="/">Return</a>
<br>
<br>
<!-- 為ECharts准備一個具備大小(寬高)的Dom -->
<div id="genre-year-sales" style="width: 1000px;height:550px;"></div>
<script type="text/javascript">
$.getJSON("static/data/genre-year-sales.json", d => {
console.log(d);
// 基於准備好的dom,初始化echarts實例
var myChart = echarts.init(document.getElementById('genre-year-sales'), 'light');
option = {
legend: {},
tooltip: {
trigger: 'axis',
showContent: false
},
dataset: {
source: [
['year', ...d['Indie'].map(v => `${v[1]}`)],
...['Indie', 'Pop', 'Rap', 'Latino', 'Pop-Rock'].map(v => [v, ...d[v].map(v1 => v1[2])])
]
},
xAxis: { type: 'category' },
yAxis: { gridIndex: 0 },
grid: { top: '55%' },
series: [
{ type: 'line', smooth: true, seriesLayoutBy: 'row' },
{ type: 'line', smooth: true, seriesLayoutBy: 'row' },
{ type: 'line', smooth: true, seriesLayoutBy: 'row' },
{ type: 'line', smooth: true, seriesLayoutBy: 'row' },
{ type: 'line', smooth: true, seriesLayoutBy: 'row' },
{
type: 'pie',
id: 'pie',
radius: '30%',
center: ['50%', '25%'],
label: {
formatter: '{b}: {@2000} ({d}%)' //b是數據名,d是百分比
},
encode: {
itemName: 'year',
value: '2000',
tooltip: '2000'
}
}
]
};
myChart.on('updateAxisPointer', function (event) {
var xAxisInfo = event.axesInfo[0];
if (xAxisInfo) {
var dimension = xAxisInfo.value + 1;
myChart.setOption({
series: {
id: 'pie',
label: {
formatter: '{b}: {@[' + dimension + ']} ({d}%)'
},
encode: {
value: dimension,
tooltip: dimension
}
}
});
}
});
// 使用剛指定的配置項和數據顯示圖表。
myChart.setOption(option);
})
</script>
</body>
</html>
這個通過讀取 code/static/data/ genre-year-sales.json
中的數據,畫出扇形圖和折線圖,分別顯示不同年份各類型專輯的銷量占總銷量的比例,和總銷量前五的專輯類型的各年份銷量變化。
(6)在code/templates目錄下新建genre-critic.html文件。復制以下代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ECharts</title>
<!-- 引入 echarts.js -->
<script src="static/js/echarts-gl.min.js"></script>
<script src="static/js/jquery.min.js"></script>
</head>
<body>
<a href="/">Return</a>
<br>
<br>
<!-- 為ECharts准備一個具備大小(寬高)的Dom -->
<div id="genre-critic" style="width: 1000px;height:550px;"></div>
<script type="text/javascript">
$.getJSON("static/data/genre-critic.json", d => {
console.log(d);
// 基於准備好的dom,初始化echarts實例
var myChart = echarts.init(document.getElementById('genre-critic'), 'light');
option = {
legend: {},
tooltip: {},
dataset: {
source: [
['genre', ...d.map(v => v[0])],
['rolling_stone_critic', ...d.map(v => v[1])],
['mtv_critic', ...d.map(v => v[2])],
['music_maniac_critic', ...d.map(v => v[3])]
]
},
xAxis: [
{ type: 'category', gridIndex: 0 },
{ type: 'category', gridIndex: 1 }
],
yAxis: [
{ gridIndex: 0 , min: 2.7},
{ gridIndex: 1 , min: 2.7}
],
grid: [
{ bottom: '55%' },
{ top: '55%' }
],
series: [
// These series are in the first grid.
{ type: 'bar', seriesLayoutBy: 'row' , barWidth: 30},
{ type: 'bar', seriesLayoutBy: 'row' , barWidth: 30},
{ type: 'bar', seriesLayoutBy: 'row' , barWidth: 30 },
// These series are in the second grid.
{ type: 'bar', xAxisIndex: 1, yAxisIndex: 1 , barWidth: 35},
{ type: 'bar', xAxisIndex: 1, yAxisIndex: 1 , barWidth: 35},
{ type: 'bar', xAxisIndex: 1, yAxisIndex: 1 , barWidth: 35},
{ type: 'bar', xAxisIndex: 1, yAxisIndex: 1 , barWidth: 35}
]
};
// 使用剛指定的配置項和數據顯示圖表。
myChart.setOption(option);
})
</script>
</body>
</html>
這個通過讀取 code/static/data/ genre-critic.json
中的數據,畫出柱形圖,顯示總銷量前五的專輯類型,在不同評分體系中的平均評分。
5)web程序啟動
① 在另一個Ubuntu終端窗口中,用 hadoop 用戶登錄,在命令行運行su hadoop,並輸入用戶密碼。
② 進入代碼所在目錄。
③ 在命令行運行如下命令:
spark-submit VisualizationFlask.py
④ 在瀏覽器打開 http://127.0.0.1:5000/
,可看到如下界面:
(1)各類型專輯的數量統計圖
從圖中可以看出Indie類型的專輯數量最多。
(2)各類型專輯的銷量統計圖
該圖統計了各個類型專輯的銷量和,從圖中可以看出Indie類型的專輯銷量最高,將近47億。Pop類型的專輯銷量排在第二,約為39億。
(3)近20年每年發行的專輯數量和單曲數量統計圖
(4)總銷量前五的專輯類型的各年份銷量分析圖
(5)總銷量前五的專輯類型的評分分析圖
5.參考資料
- 數據科學工具速查 | Spark使用指南(RDD版) https://www.showmeai.tech/article-detail/106
- 數據科學工具速查 | Spark使用指南(SQL版) https://www.showmeai.tech/article-detail/107
【大數據技術與處理】推薦閱讀
- 圖解大數據 | 大數據生態與應用導論
- 圖解大數據 | 分布式平台Hadoop與Map-Reduce詳解
- 圖解大數據 | Hadoop系統搭建與環境配置@實操案例
- 圖解大數據 | 應用Map-Reduce進行大數據統計@實操案例
- 圖解大數據 | Hive搭建與應用@實操案例
- 圖解大數據 | Hive與HBase詳解@海量數據庫查詢
- 圖解大數據 | 大數據分析挖掘框架@Spark初步
- 圖解大數據 | 基於RDD大數據處理分析@Spark操作
- 圖解大數據 | 基於Dataframe / SQL大數據處理分析@Spark操作
- 圖解大數據 | 使用Spark分析新冠肺炎疫情數據@綜合案例
- 圖解大數據 | 使用Spark分析挖掘零售交易數據@綜合案例
- 圖解大數據 | 使用Spark分析挖掘音樂專輯數據@綜合案例
- 圖解大數據 | Spark Streaming @流式數據處理
- 圖解大數據 | 工作流與特征工程@Spark機器學習
- 圖解大數據 | 建模與超參調優@Spark機器學習
- 圖解大數據 | GraphFrames @基於圖的數據分析挖掘
ShowMeAI系列教程推薦
- 大廠技術實現方案系列
- 圖解Python編程:從入門到精通系列教程
- 圖解數據分析:從入門到精通系列教程
- 圖解AI數學基礎:從入門到精通系列教程
- 圖解大數據技術:從入門到精通系列教程
- 圖解機器學習算法:從入門到精通系列教程
- 機器學習實戰:手把手教你玩轉機器學習系列
- 深度學習教程:吳恩達專項課程 · 全套筆記解讀
- 自然語言處理教程:斯坦福CS224n課程 · 課程帶學與全套筆記解讀
- 深度學習與計算機視覺教程:斯坦福CS231n · 全套筆記解讀