基于GraphLab的图分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lusongno1/article/details/81090336

基于GraphLab的图分析

问题的确定

首先我从kaggle yelp dataset下载了数据。数据主要包含7个csv文件。我们把数据导进来看看长什么样子。

  • yelp_business是商铺的一些信息,包括’business_id’,’name’,’neighborhood’,’address’,’city’,’state’,’postal_code’, ‘latitude’, ‘longitude’, ‘stars’,’review_count’ ,’is_open’,’categories’等条目,还有很多缺失值。

  • 读入yelp_business_hours,发现这是商铺从周一到周日每天的营业时间。

  • yelp_checkin是商铺的登入的时间和数目。

  • yelp_review读取的时间较长,主要是用户对于商户的评价,及对于评价的一个回馈。

  • yelp_tip也是一些评价文本,包括’text’, ‘date’, ‘likes’, ‘business_id’, ‘user_id’等。

  • yelp_user是用户的一些信息,包括’user_id’, ‘name’, ‘review_count’, ‘yelping_since’, ‘friends’, ‘useful’, ‘funny’, ‘cool’, ‘fans’, ‘elite’, ‘average_stars’, ‘compliment_hot’, ‘compliment_more’, ‘compliment_profile’, ‘compliment_cute’, ‘compliment_list’, ‘compliment_note’, ‘compliment_plain’, ‘compliment_cool’, ‘compliment_funny’, ‘compliment_writer’, ‘compliment_photos’等信息。包括其一些评价,还有朋友是谁等等。

  • 同样地,我们读入yelp_business_attributes数据,发现其由’business_id’, ‘AcceptsInsurance’, ‘ByAppointmentOnly’, ‘BusinessAcceptsCreditCards’, ‘BusinessParking_garage’, ‘BusinessParking_street’, ‘BusinessParking_validated’, ‘BusinessParking_lot’, ‘BusinessParking_valet’, ‘HairSpecializesIn_coloring’, ‘HairSpecializesIn_africanamerican’, ‘HairSpecializesIn_curly’, ‘HairSpecializesIn_perms’, ‘HairSpecializesIn_kids’, ‘HairSpecializesIn_extensions’, ‘HairSpecializesIn_asian’, ‘HairSpecializesIn_straightperms’, ‘RestaurantsPriceRange2’, ‘GoodForKids’, ‘WheelchairAccessible’, ‘BikeParking’, ‘Alcohol’, ‘HasTV’, ‘NoiseLevel’, ‘RestaurantsAttire’, ‘Music_dj’, ‘Music_background_music’, ‘Music_no_music’, ‘Music_karaoke’, ‘Music_live’, ‘Music_video’, ‘Music_jukebox’, ‘Ambience_romantic’, ‘Ambience_intimate’, ‘Ambience_classy’, ‘Ambience_hipster’, ‘Ambience_divey’, ‘Ambience_touristy’, ‘Ambience_trendy’, ‘Ambience_upscale’, ‘Ambience_casual’, ‘RestaurantsGoodForGroups’, ‘Caters’, ‘WiFi’, ‘RestaurantsReservations’, ‘RestaurantsTakeOut’, ‘HappyHour’, ‘GoodForDancing’, ‘RestaurantsTableService’, ‘OutdoorSeating’, ‘RestaurantsDelivery’, ‘BestNights_monday’, ‘BestNights_tuesday’, ‘BestNights_friday’, ‘BestNights_wednesday’, ‘BestNights_thursday’, ‘BestNights_sunday’, ‘BestNights_saturday’, ‘GoodForMeal_dessert’, ‘GoodForMeal_latenight’, ‘GoodForMeal_lunch’, ‘GoodForMeal_dinner’, ‘GoodForMeal_breakfast’, ‘GoodForMeal_brunch’, ‘CoatCheck’, ‘Smoking’, ‘DriveThru’, ‘DogsAllowed’, ‘BusinessAcceptsBitcoin’, ‘Open24Hours’, ‘BYOBCorkage’, ‘BYOB’, ‘Corkage’, ‘DietaryRestrictions_dairy-free’, ‘DietaryRestrictions_gluten-free’, ‘DietaryRestrictions_vegan’, ‘DietaryRestrictions_kosher’, ‘DietaryRestrictions_halal’, ‘DietaryRestrictions_soy-free’, ‘DietaryRestrictions_vegetarian’, ‘AgesAllowed’, ‘RestaurantsCounterService’等属性,都是一些bool值,包括是否有停车场,是否接受保险等等。

这些数据和信息中,我比较感兴趣的是yelp_user中含有这些顾客的朋友是谁这个信息。所以,基于此,我想做一个基于图的社交网络分析。主要想看看这些人分成了几个群体,每个人的朋友多不多等情况……


数据的选择、预处理

导入graphlab模块,导入数据,预览,取样本。

import graphlab
yelp_user = graphlab.SFrame.read_csv('yelp-dataset\yelp_user.csv',verbose = False)
yelp_user.head(1)

因为数据过大,在我的机子上跑不了,那么取部分样本来跑。可以去租借aws的平台来跑,但是我没有钱,感觉这个事情也没有这个必要。先取小部分数试一下。

users = yelp_user.sample((100.0/len(yelp_user)),seed=5)
len(users)

因为users的friends列是字符串格式的,每个字符串用逗号分割好友名称。我们想办法把字符串处理成list,便于后续处理。首先要定义一个分割字符串的函数。

def split(str):
    list = str.split(',')
    return list
users['friends_split'] = users['friends'].apply(split)

我们使用SFrame的flat_map方法,构建一个列名为[“user_id”, “friend_id”]的SFrame。这里的fn其实是基于SFrame一行的一个function,flat_map将它apply到了整个SFrame。fn中的for循环不过是将前方list构造简单堆叠起来,并入到最后的结果中。

edges = users.flat_map(column_names=["user_id", "friend_id"], fn=lambda x: [[x['user_id'], i] for i in x['friends_split']])
#Map each row of the SFrame to multiple rows in a new SFrame via a function.
#yelp_user['friends_split'] = yelp_user['friends'].apply(split)
#yelp_edges = yelp_user.flat_map(column_names=["user_id", "friend_id"], fn=lambda x: [[x['user_id'], i] for i in x['friends_split']])
len(edges)
edges.head(3)

现在我们delete掉users中原有的frieds和frends_split列,因为我们把相关信息存到了edges当中。

del users['friends']
del users['friends_split']
print "Number of edges {}".format(len(edges))
print "Number of users {}".format(len(users))

我们把数据保存下来,以备不时之需。所谓的不时之需,就是防止重跑代码的情况。如果数据量大的话,这是很有意义的一步。

edges.save('yelp-dataset\edges')
users.save('yelp-dataset\users')

图模型的建立

我们将在这一步建立两个模型:基于连通分量计算的模型和基于度数计算的模型,用以分析社交分群情况,和个体社交情况。进一步,还可以做一些社区发现的工作。

import graphlab.aggregate as agg
edges = graphlab.load_sframe("yelp-dataset/edges/")
users = graphlab.load_sframe("yelp-dataset/users/")
edges.head(3)
len(edges['user_id'].unique())
len(edges['friend_id'].unique())
users.head(1)
len(edges)
len(users)
构建一个connected_components图模型
uf_graph = graphlab.SGraph(users, edges, 'user_id', 'user_id', 'friend_id')

想做一个图的可视化,奈何数据量太大,根本画不出来,我们用get_neighborhood方法做一个子集的可视化。

user_id = users['user_id']
targets = user_id[0:3]
targets
subgraph = uf_graph.get_neighborhood(ids=targets, radius=1, full_subgraph=True)
subgraph.show(arrows=True)

结果如下图所示,我们看到只有三个群,有一些单向的箭头。出现这个结果的原因主要是数据样本太大了,而我们只是取了其中很少的一部分(3个),这3个人当中就很有可能相互之间没有朋友关系。

这里写图片描述

因为数据量比较大,我们做不了完全的可视化,但是还是可以看一下简单的摘要信息的。

uf_graph.summary()

在图论中,无向图的连通分量(或仅仅是分量)是一个子图,其中任何两个顶点通过路径相互连接,并且在超图中没有连接到其他顶点。我们搭建一个连通分量模型。从结果中可以看到,连通子图的集合有三千多个顶点,三千多条边,形成了57个连通子图。我们可以看到,每个顶点是属于哪个ID的连通子图的。

cc = graphlab.connected_components.create(uf_graph)
cc.show()

这里写图片描述

因为数据量太大,我们做不了cc的可视化,但是我们还是可以看到一些摘要信息的,有时候,这对我们来说已经很足够了。

cc.summary()
cc_df = cc.component_id
cc_df.head()

我们使用高端大气的groupby来数一数每个连通子图下都有多少个成员。显然,我们自己计算的记过和component_size方法的结果是一致的。

component_counts = cc_df.groupby("component_id", operations={'count':agg.COUNT()})
component_counts.head(3)
cc.component_size[cc.component_size['component_id']==310]
cc.graph

所谓物以类聚,人以群分。我们来看看,聚起来数目超过一定量的连通子图有哪些。

component_counts[component_counts['count']>100]

可以用sketch_summary看一下连通子图的相关的统计信息。

component_counts['count'].sketch_summary()
接下来我们搭建一个计算入度和出度的模型。
dc = graphlab.degree_counting.create(uf_graph)
vertices = dc.graph.get_vertices()
vertices.head(1)
vertices['total_degree'].sketch_summary()

考虑哪些入度和出度不等的那些个体,虽然说我们选的样本不到一百个,但是究其朋友,虽然不在样本中,也算是一个顶点,这么来说,数目就多了去了。

# Vertices where in degree != out degree, implying directed graph
print vertices[vertices['out_degree']!=vertices['in_degree']].head()
print "Number of vertices where in-degree != out-degree: {}".format(len(vertices[vertices['out_degree']!=vertices['in_degree']]))

snap的使用

接下来,我用一个神器snap来做进一步的分析,只是一个大规模数据与复杂网络分析的package。
Let’s use a community detection method from the snap.py library here. This is a very highly optimized library for doing analysis of networks in python

Approach

We will use an approximate community detection method to find distinct groups of friends.

Changing the dataset

  • We can safely remove users with no friends
  • Make this graph an undirected graph
  • convert user_ids to numbers (as required by the snap library)
import snap
#Choosing vertices with non zero degree
vertices = vertices[vertices['total_degree']>0]
len(vertices)

我们自己搞一个从1开始的索引,毕竟顶点的名字太长了,不好使。

v_index = graphlab.SArray(range(1, len(vertices)+1))
v_index.head(3)
vertices = vertices.add_column(v_index, name='index')
vertices.head(3)

我们在eges上面把users和friends对应的索引给加上。

#del edges['user_num']
#del edges['friend_num']
edges.add_columns([graphlab.SArray([0]*len(edges)), graphlab.SArray([0]*len(edges))], ['user_num', 'friend_num'])

以__id为索引,转vertices信息为pandas结构,为了进一步的处理。

# Converted to a pandas data frame with the original id as index for quick lookup
vdf = vertices['__id', 'index'].to_dataframe().set_index("__id")

使用pandas的索引,轻松地将edges中的user和friend加上对应的索引。

# Updating the user_num and friend_num columns with the numeric id
edges['user_num'] = vdf.loc[edges['user_id']]['index']
edges['friend_num'] = vdf.loc[edges['friend_id']]['index']

开始的时候,因为我们的vertices选取的是构建的图中,总度数大于1的点,且由于我们为了计算速度,很不合理地只选取了部分数据,故而大部分的friend点是给过滤掉了,故而它们的索引缺失了。故只过滤总度为0的点。

edges

把来之不易的带索引的edges数据和vertices数据存下来,以备不时之需。

edges.save("yelp-dataset/snap_edges")
vertices.save("yelp-dataset/snap_vertices")
edges = graphlab.load_sframe("yelp-dataset/snap_edges")
vertices = graphlab.load_sframe("yelp-dataset/snap_vertices")

我们使用snap创建一个有向图,顶点个数同原来,边数同原来。

ug = snap.TNGraph.New(len(vertices), len(edges))
#edges.fillna('user_num',0)
#edges.fillna('friend_num',0)
#dir(ug)

把原来的所有节点都加进去。

## Add all the nodes
for i in range(len(vertices)):
    ug.AddNode(vertices['index'][i])

把原来的边都加进去

## Add Edges
for i in range(len(edges)):
    ug.AddEdge(int(edges['user_num'][i]),int(edges['friend_num'][i]))

上面加边的方式很耗时,我们可以使用如下方式加边。
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

# Horrible approach takes too long. Do something else ! 
for u,f in zip(edges['user_num'],edges['friend_num']):
    ug.AddEdge(u,int(f))
##zip(edges['user_num'],edges['friend_num'])

图表允许我们通过关注个体之间的关系来理解复杂的网络。每个个体由图形中的顶点表示,个体之间的关系由边缘表示。为了促进面向图形的数据分析,GraphLab Create提供了一个 SGraph 对象——一个由SFrame支持的可伸缩图形数据结构。图的连通分量其实是子图,其中每对节点通过组件的某个路径连接。对于不同连接组件中的两个节点,它们之间没有路径。

猜你喜欢

转载自blog.csdn.net/lusongno1/article/details/81090336
今日推荐