未加星标

如何利用维基百科的数据可视化当代音乐史

字体大小 | |
[商业智能 所属分类 商业智能 | 发布者 店小二03 | 时间 | 作者 红领巾 ] 0人收藏点击收藏

“我相信马塞勒斯·华莱士,我的丈夫,你的老板吩咐你带我出门做我想做的任何事。现在,我想跳舞,我要赢,我想得到那个奖杯,把舞跳好来!”


如何利用维基百科的数据可视化当代音乐史

《黑色追缉令》是我一直以来最喜欢的电影。令人惊奇的故事情节、演员、表演以及导演会让我想要前去影院观看,当别人问起“你看过这部电影吗?”,我可以打破僵局。电影中最具标志性的场景可能是乌玛•瑟曼和约翰•特拉沃尔塔在杰克兔子餐厅的舞池跳扭扭舞的那段。虽然这可能是乌玛•瑟曼最经典的舞蹈场景,但约翰•特拉沃尔塔似乎根本停不下来,在电影《迈克》、《发胶》、《黑色追缉令》、《油脂》、《周末夜狂热》和《都市牛郎》中约翰所饰演的角色总是梳着锃亮的大背头、乌黑的头发、极富本性地跳着舞。


虽然很多人可能会笑约翰在舞池中央跟着迪斯科音乐跳舞的场景,但扪心自问,所有酷酷的舞蹈电影是否都注定是相同的。随着时间流逝我们是否还会被《魅力四射》(Bring it On,美国系列青春校园电影——译者注)和《街舞少年》(Stompthe Yard)中的音乐所感动?如果看一看这些年最流行音乐风格的变化趋势(如下图),大众对流行乐偏好的变化似乎没有迪斯科的节奏那么快。

可视化


如何利用维基百科的数据可视化当代音乐史

通过分析Billboard年终榜单中前100首歌曲,我们可以根据每年Billboard上最流行歌曲所代表的音乐风格的份额来量化现代音乐的走向。图中我们可以看出,迪斯科(Disco)只有短短十几年的光辉,从90年代以来饶舌(Rap)和嘻哈(Hip-Hop)音乐风格才持续出现。有趣的是,本世纪初随着历史的重复,饶舌和嘻哈音乐处于巅峰,迪斯科的变动与流行音乐中一些最低份额的流派保持一致。慢摇滚(Soft Rock)和硬摇滚(HardRock)的光景甚至比迪斯科更糟糕,在2005年完全灭绝。相反的是,麦当娜在2005年的复兴单曲继续延续了迪斯科的影响力,在2010年后,我们被火星哥(Bruno Mars)和魔力红(Maroon 5)的歌洗脑。


如何利用维基百科的数据可视化当代音乐史

这一可视化视图是如何绘制而成的?

维基百科是一座金矿,里面有列表,列表里面套着列表,甚至被套着的列表里面还套着列表。其中一个列表恰巧是Billboard最热门的100首单曲,它使我们能够很容易地浏览维基百科的数据。在快速查看网址后,我们能够简单地生成页面,从中爬取数据,这样更简单。我们从为程序加载必要的模块和参数开始。

#ipython 内联查看画图并导入必要的包

import numpy as np

import pandas as pd

import seaborn as sns

import pylab as pylab

import matplotlib.pyplot as plt

import requests, cPickle, sys, re, os

from bs4 import BeautifulSoup as bs

import logging

logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s',level=logging.INFO)

requests = requests.Session()

# 设置画板大小

pylab.rcParams['figure.figsize'] = 32, 16

接着程序脚本利用我们在网址中找到的模式,尝试从页面中提取所有可能存在的链接。


# 定义一个从维基百科表格中抓取相关信息的函数,

如果没有返回NaN

def tryInstance(td, choice):

try:

# 歌曲只有一个维基百科链接,但是歌手可能有许多链接。

我们创建一个选择标志, #用来决定抓取文本信息还是链接信息

if (choice == 0):

return td.text

elif (choice == 1):

links = [x['href'] for x in td.findAll('a')]

if (len(links) != 0):

return links

else:

return float('NaN')

except:

return float('NaN')

#找到页面的第一个table,尽量抓取所有表格行的信息

pandaTableHeaders = ['year', 'pos', 'song','artists', 'song_links', 'artist_links']

headers = {'User-Agent': 'Mozilla/5.0 (X11;linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116Safari/537.36'}

cookies = {'cookie': 'CP=H2;WMF-Last-Access=16-Apr-2016; GeoIP=CN:01:Xuancheng:30.95:

118.76:v4;enwikimwuser-sessionId=588324132f65192a'}

def scrapeTable(year):

#创建url路径,用BeautifulSoup解析页面内容,创建列表用来存储表数据

url ='https://en.wikipedia.org/wiki/Billboard_Year-End_Hot_100_singles_of_'+str(year)

soup =bs(requests.get(url, headers=headers, cookies=cookies).content)

table = []

#由于文本格式的不同,我们针对4种特例使用不同的code来创建临时souptable变量

souptable= soup.find('table')

if (year in [2006, 2012, 2013]): souptable = soup.findAll('table')[1] elif (year in [2011]): souptable = soup.findAll('table')[4]

#从上面迭遍历程序得到的table中收集每个表格行的信息

for pos, tr in enumerate(souptable.findAll('tr')):

tds = tr.findAll('td')

if (len(tds) > 0):

toAppend = [

year, pos,

tryInstance(tds[-2], 0),tryInstance(tds[-1], 0), tryInstance(tds[-2], 1),tryInstance(tds[-1], 1)

]

table.append(toAppend)

#创建并返回表数据的数据框形式

df = pd.DataFrame(table)

df.columns = pandaTableHeaders

return df

#遍历所有可能的年份,序列化存储,方便以后使用

dfs =pd.DataFrame(pandaTableHeaders).set_index(0).T

for year in xrange(1956, 2016):

print year,

dfs = dfs.append(scrapeTable(year))

cPickle.dump(dfs.reset_index().drop('index',axis=1), open('wikipediaScrape.p', 'wb'))

借助存储在数据帧中的所有链接,我们可以加载每个维基百科页面,并从每一页右上角信息表中提取信息。不幸的是,当所有这些信息表的长度不同,有不同的 HTML 嵌套和不完整数据时,这些数据会变得特别混杂(竟然没有人将Gorillaz 音乐进行归类?!)。


如何利用维基百科的数据可视化当代音乐史

为了解决这一问题,我们在代码中查找表对象,并将其作为字符串保存并在之后的分析进行加载。这样做的优点是加倍的,它可以让我们从一次运行中收集所有必要的信息;同时,也帮助我们从用户的定义中对音乐流派关键词进行分类。


#从wikipediaScrape.p文件中加载数据框,创建新的列,边抓取信息边填充

dfs =cPickle.load(open('wikipediaScrape.p', 'rb'))

subjects =['Genre', 'Length', 'Producer', 'Label', 'Format', 'Released', 'B-side']

for subject insubjects:

dfs[subject] = float('NaN')

# 与上面的tryInstance函数类似,尽可能抓取更多信息

# 捕获缺失异常,使用NaNs替代缺失值

# 另外,还有一个问题是tables难于管理。其内容可能存在或不存在,可能有错别字

# 或不同的名字。

def extractInfoTable(url):

infoTable = []

#捕获表头、表行和页面异常

try:

soup = bs(requests.get(url, headers=headers, cookies=cookies).content)

for tr in soup.find('table').findAll('tr'):

try:

header = tr.find('th').text

if (header == 'Music sample'):

# Music sample表示信息表的结束,如果满足条件中断循环以节省时间

break

try:

# 如果表头不是Musicsample,收集”tr”对象中所有可能的信息

trs = tr.findAll('td')

infoTable.append([header,trs])

except:

noTrsFound = True

except:

noHeaderFound = True

except:

noPageFound = True

#如果subjects列表中存在记录,保存HTML字符串形式

infoColumns = []

for subject in subjects:

instanceBool = False

for header, info in infoTable:

if (subject in header):

infoColumns.append([subject,str(info)])

instanceBool = True

break

if (not instanceBool):

infoColumns.append([subject,float('NaN')])

#返回所有抓取的信息

return infoColumns

#对数据帧中所有的歌曲使用scraping函数

forsongIndex in xrange(0,dfs.shape[0]): printsongIndex, dfs.ix[songIndex].year, dfs.ix[songIndex].song

try:

# 获取链接

song_links =['https://en.wikipedia.org' + x for x in dfs.ix[songIndex].song_links]

# 抽取信息

logging.info('extract info')

infoTable =extractInfoTable(song_links[0])

# 存储index和subjectstore信息

for idx, subject in enumerate(subjects):

dfs.loc[:,(subject)].ix[songIndex]= str(infoTable[idx][1])

#每100首歌曲序列化保存

if (songIndex % 100 == 0):

cPickle.dump(dfs.reset_index().drop('index', axis=1), open('full_df.p','wb'))

except(TypeError):

print 'NaN link found'

# 保存所有的数据帧信息

cPickle.dump(dfs.reset_index().drop('index',axis=1), open('full_df.p', 'wb'))

现在,我们开始对所有HTML字符串进行分析。当音乐流派可以被识别时,我们就可以抽取关键词列表,之后将它们分入“脏列表”(脏,表示数据还未被清洗——译者注)。这一列表充满了错别字、名称不统一的名词、引用等等。

#创建流派字典,比如,对于“folk”和“country”范围的分析则认为是相同的音乐流#派

genreList= {

'electronic': ['electronic'], 'latin' : ['latin'], 'reggae' : ['reggae'], 'pop' : ['pop'], 'dance' : ['dance'], 'disco' : ['disco', 'funk'], 'folk' : ['folk', 'country'], 'r&b' : ['r&b'], 'blues' : ['blues'], 'jazz' : ['jazz'], 'soul' : ['soul'], 'rap' : ['rap', 'hip hop'], 'metal' : ['metal'], 'grunge' : ['grunge'], 'punk' : ['punk'], 'alt' : ['alternative rock'], 'soft rock' : ['soft rock'], 'hard rock' : ['hard rock'],

}

#加载数据帧并抽取相关的流派

# 添加“dirty”列,名单包括HTML元素

# “ dirty”列包含的错别字、引用等记录都会导致异常发生,但是我们感兴趣的是从

# 混乱的字符串中抽取相关的关键字,通过简单匹配所有的小写实例,计数最后的

#“pop”流派个数

df =cPickle.load(open('full_df.p', 'rb'))

defextractGenre(x):

sx = str(x)

try:

dirtyList = [td.text.replace('\n', '')for td in BeautifulSoup(sx).findAll('td')]

return dirtyList

except:

return float('NaN')

df['Genre']= df['Genre'].apply(extractGenre) # 打印df['Genre']

最后我们为每首歌所代表的音乐流派创建标志列,使绘制图片更加容易。

#添加”key”列,如果key是流派字典的键值则为1,否则为0。拷贝数据帧,使

#用.loc[(tuple)]函数以避免切片链警告。

for keyin genreList.keys():

df[key] = 0

dfs =df.copy()

# 对于genreList字典中每个流派匹配字符串,如果能匹配,则标志指定列,以便能够在后面输出布尔结果

forgenre in genreList:

ans=0

for idx in xrange(0, df.shape[0]): if (len(df.loc[(idx,'Genre')]) > 0): if (any([x indf.loc[(idx,'Genre')][0].lower() for x in genreList[genre]])): dfs.loc[(idx, genre)] = 1

ans+=1

print genre, ans

sys.stdout.flush()

cPickle.dump(dfs,open('genre_df.p', 'wb'))


微调变量后导出数据

df =cPickle.load(open('genre_df.p', 'rb'))

defaverageAllRows(gdf):

# 添加”sums”列

gdf['sums'] = gdf.sum(axis=1)

#对数据帧的每列除以”sums”列,添加精度1e-12,排除分母为零的情况

logging.info('averageAllRows')

for col in gdf.columns:

gdf[col] =gdf[col].divide(gdf['sums']+1e-12)

#返回数据帧并丢弃”sums”列

return gdf.drop('sums', axis=1)


pylab.rcParams['figure.figsize']= 32, 16

gdf =pd.DataFrame()

for g ingenreList.keys():

gdf[g] = df.groupby('year')[g].sum()

# 自定义打印顺序

gl2 = [

'jazz', 'blues', 'folk', 'soul', 'pop','disco', 'rap', 'soft rock',

'hard rock', 'dance', 'r&b', 'alt','latin', 'reggae', 'electronic', 'punk',

'grunge', 'metal',

]

#对数据帧重新排序并对所有行求平均

gdf =gdf[gl2]

gdf =averageAllRows(gdf)

# 创建百分比条形图

ax =gdf.plot(kind='bar', width=1,stacked=True, legend=False, cmap='Paired',linewidth=0.1)

ax.set_ylim(0,1)

ax.legend(loc='centerleft', bbox_to_anchor=(1, 0.5))

locs,labels = plt.xticks()

plt.setp(labels,rotation=90)

plt.show()


最后的输出


如何利用维基百科的数据可视化当代音乐史

编后语

由于程序是对1956年-2016年期间的Wiki年度热门歌手页面的爬取,处理过程很耗时,因此,我们将1956-2016时间段分成了6部分,每部分包含了跨度为10年的年度热门歌手页面的处理。具体方法是将”for year in xrange(1956, 2016)”程序修改为” foryear in xrange(1956, 1966)”等。您也可以使用我们训练好的模型进行验证,模型文件genre_df.p已按照年份保存到对应目录了,在加载模型文件的目录地址一定不要写错了。

df =cPickle.load(open('./06_16/genre_df.p', 'rb'))

defaverageAllRows(gdf):

# 添加”sums”列

gdf['sums'] = gdf.sum(axis=1)

#对数据框的每列除以”sums”列,添加精度1e-12,排除分母为零的情况

logging.info('averageAllRows')

for col in gdf.columns:

gdf[col] =gdf[col].divide(gdf['sums']+1e-12)

#返回数据框并丢弃“sums”列

return gdf.drop('sums', axis=1)


pylab.rcParams['figure.figsize']= 32, 16

gdf =pd.DataFrame()

for g ingenreList.keys():

gdf[g] = df.groupby('year')[g].sum()

# 自定义打印顺序

gl2 = [

'jazz', 'blues', 'folk', 'soul', 'pop','disco', 'rap', 'soft rock',

'hard rock', 'dance', 'r&b', 'alt','latin', 'reggae', 'electronic', 'punk',

'grunge', 'metal',

]

#对数据框重新排序并对求平均

gdf =gdf[gl2]

gdf =averageAllRows(gdf)

# 创建百分比条形图

ax =gdf.plot(kind='bar', width=1,stacked=True, legend=False, cmap='Paired',linewidth=0.1)

ax.set_ylim(0,1)

ax.legend(loc='centerleft', bbox_to_anchor=(1, 0.5))

locs,labels = plt.xticks()

plt.setp(labels,rotation=90)

plt.show()


欢迎加入本站公开兴趣群

商业智能与数据分析群

兴趣范围包括各种让数据产生价值的办法,实际应用案例分享与讨论,分析工具,ETL工具,数据仓库,数据挖掘工具,报表系统等全方位知识

QQ群:418451831

tags: #160,gdf,df,dfs,year,sums,return,rock,cPickle,table,数据,index
分页:12
转载请注明
本文标题:如何利用维基百科的数据可视化当代音乐史
本站链接:https://www.codesec.net/view/429446.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 商业智能 | 评论(0) | 阅读(298)