背景:有个需求是把几个行为发生的时间在一定范围的,分到一个团体,比如两个人做这件事的时间差是在15分钟以内,那就算是一个团体。另外一个人和这两个时间比较,如果是在15分钟以内,那他也是这个团体的。难点是,要确定来的每一个人都要和团体里已有的人的时间内比较到,万幸是时间是有序的。一开始想的太简单,本来想用sql语句直接搞出来,结果就跑远了。用java吧,一堆东西下去也差不多。所以就想着加个python用算法搞,网上找了一遍聚类的算法。发现DBSCAN挺符合需求的,虽然我这个都用不到多少它的优点…
DBSCAN介绍(原处)一开始在网上看到一个挺不错的聚类算法介绍博客:https://blog.csdn.net/u011511601/article/details/81951939
很易懂。本来没注意那个笑脸的,后来找一圈回来,发现维度控制成一维的就很符合我要的东西了。
DBSCAN(Density-based Spatial Clustering of Applications with Noise,具有噪声的基于密度的聚类方法)是一种基于密度的空间聚类算法。 该算法将具有足够密度的区域划分为簇,并在具有噪声的空间数据库中发现任意形状的簇,它将簇定义为密度相连的点的最大集合。
有个算法体验的网址,可以去玩玩。https://www.naftaliharris.com/blog/visualizing-dbscan-clustering/
1.算法的含义,与其他聚类算法的差别文字看不懂看下面这个图。下面这些点是分布在样本空间的众多样本,现在我们的目标是把这些在样本空间中距离相近的聚成一类。我们发现A点附近的点密度较大,红色的圆圈根据一定的规则在这里滚啊滚,最终收纳了A附近的5个点,标记为红色也就是定为同一个簇。其它没有被收纳的根据一样的规则成簇。(形象来说,我们可以认为这是系统在众多样本点中随机选中一个,围绕这个被选中的样本点画一个圆,规定这个圆的半径以及圆内最少包含的样本点,如果在指定半径内有足够多的样本点在内,那么这个圆圈的圆心就转移到这个内部样本点,继续去圈附近其它的样本点,类似传销一样,继续去发展下线。等到这个滚来滚去的圈发现所圈住的样本点数量少于预先指定的值,就停止了。那么我们称最开始那个点为核心点,如A,停下来的那个点为边界点,如B、C,没得滚的那个点为离群点,如N)。
基于密度这点有什么好处呢,我们知道kmeans聚类算法只能处理球形的簇,也就是一个聚成实心的团(这是因为算法本身计算平均距离的局限)。但往往现实中还会有各种形状,比如下面两张图,环形和不规则形,这个时候,那些传统的聚类算法显然就悲剧了。于是就思考,样本密度大的成一类呗。这就是DBSCAN聚类算法。
上面提到了红色圆圈滚啊滚的过程,这个过程就包括了DBSCAN算法的两个参数,这两个参数比较难指定,公认的指定方法简单说一下:
半径:半径是最难指定的 ,大了,圈住的就多了,簇的个数就少了;反之,簇的个数就多了,这对我们最后的结果是有影响的。我们这个时候K距离可以帮助我们来设定半径r,也就是要找到突变点,比如:
以上虽然是一个可取的方式,但是有时候比较麻烦 ,大部分还是都试一试进行观察,用k距离需要做大量实验来观察,很难一次性把这些值都选准。
MinPts:这个参数就是圈住的点的个数,也相当于是一个密度,一般这个值都是偏小一些,然后进行多次尝试
DBSCAN运用我也只是个小白,代码什么的将就看一下吧
from urllib import parse import pymysql # 打开数据库,参数依次为:主机名/IP,用户名,密码,数据库名,字符集 db = pymysql.connect(host="", user="", password="", charset='utf8') # 使用 cursor() 方法创建一个游标对象 cursor cursor = db.cursor() # 数据准备 # 使用 execute() 方法执行 SQL 查询 cursor.execute( """ 这里是直接查出你要分析的数据 """) import pandas as pd result = cursor.fetchall() frame = pd.Dataframe(list(result)) frame.columns = ['columns1', 'columns2', 'columns3', 'create_date', 'update_date', 'rn', 'time_type', 'rownumber'] # 算法分析数据 F = pd.Dataframe() i = 1 # 循环将不同分组('rownumber')的数据放入计算 while i <= frame['rownumber'].max(): _frame = frame[frame['rownumber'] == i].copy() X = _frame[['time_type', 'bid_id', 'create_date', 'update_date', 'rownumber']] # 半径是最难分析出来的,只有一个维度的话,很好理解,多维度就很难确定,我这里本来是只有一个差异维度的。后来又加了一个,这时直接用时间戳当距离就已经有点不规范了,算是小白试个水 # 设置半径为900(把时间改成时间戳,在数轴上有一定顺序,也符合距离计算),最小样本量为1(半径内另外有一个算是一个聚类),建模 dsc = DBSCAN(eps=900, min_samples=1).fit(X) labels = dsc.labels_ _frame['group_no'] = labels # 在数据集最后一列加上经过DBSCAN聚类后的结果 _frame.sort_values('group_no') F = F.append(_frame) i += 1 # print(F) # 展示数据 # 删除表中数据,为插入做准备,现在是每日全量 sql = "DELETE FROM db.table " try: # 执行SQL语句 cursor.execute(sql) # 提交修改 db.commit() except: # 发生错误时回滚 db.rollback() # 关闭连接 cursor.close() db.close() # 将结果集导入数据库 from sqlalchemy import create_engine # Dataframe插入数据,踩了不少坑 engine = create_engine('mysql+pymysql://user:%s@post/db ?charset=utf8'%parse.quote_plus('psw')) # 可用于to_sql和read_sql frame.to_sql(name='table',con = engine,if_exists = 'append',index = False,index_label = False) print("更新成功")
代码不重要,只是提供一个参考,理解最重要,怎么运用和得到自己需要的结果还要好好学习。我也只是初步阶段,欢迎指正
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)