条目和列之间的Pyspark欧氏距离

条目和列之间的Pyspark欧氏距离,pyspark,euclidean-distance,Pyspark,Euclidean Distance,我正在使用pyspark,想知道是否有什么聪明的方法可以在数组的一行条目和整个列之间获得欧几里德数据量。例如,有这样一个数据集 +--------------------+---+ | features| id| +--------------------+---+ |[0,1,2,3,4,5 ...| 0| |[0,1,2,3,4,5 ...| 1| |[1,2,3,6,7,8 ...| 2| 选择其中一列,即id==1,并计算欧几里德距离

我正在使用pyspark,想知道是否有什么聪明的方法可以在数组的一行条目和整个列之间获得欧几里德数据量。例如,有这样一个数据集

+--------------------+---+
|            features| id|
+--------------------+---+
|[0,1,2,3,4,5     ...|  0|
|[0,1,2,3,4,5     ...|  1|
|[1,2,3,6,7,8     ...|  2|
选择其中一列,即id==1,并计算欧几里德距离。在这种情况下,结果应该是[0,0,sqrt(1+1+1+9+9)]。 有人能想出有效的方法吗?
谢谢

您可以执行
bucketedrandomprojectionsh
[1]来获得数据帧之间的笛卡尔距离

from pyspark.ml.feature import BucketedRandomProjectionLSH

brp = BucketedRandomProjectionLSH(
    inputCol="features", outputCol="hashes", seed=12345, bucketLength=1.0
)
model = brp.fit(df)
model.approxSimilarityJoin(df, df, 3.0, distCol="EuclideanDistance")
one_row = df.where(df.id == 1).first().features
model.approxNearestNeighbors(df2, one_row, df.count()).collect()
您还可以使用
approxNearestNeighbors
[2]获取一行到列的距离,但结果受到
numNearestNeighbors
的限制,因此您可以为其提供整个数据帧的计数

from pyspark.ml.feature import BucketedRandomProjectionLSH

brp = BucketedRandomProjectionLSH(
    inputCol="features", outputCol="hashes", seed=12345, bucketLength=1.0
)
model = brp.fit(df)
model.approxSimilarityJoin(df, df, 3.0, distCol="EuclideanDistance")
one_row = df.where(df.id == 1).first().features
model.approxNearestNeighbors(df2, one_row, df.count()).collect()
另外,请确保将数据转换为向量

from pyspark.sql import functions as F

to_dense_vector = F.udf(Vectors.dense, VectorUDF())
df = df.withColumn('features', to_dense_vector('features'))
[1]


[2]

如果您只需要在数据帧中的一行和每一行之间找到欧几里德距离,那么您可以过滤和收集该行,并将其传递给
udf

但是,如果需要计算所有对之间的距离,则需要使用join。
按id重新划分数据帧,将加快连接操作。不需要计算完全成对矩阵,只需计算上半部分或下半部分并复制它。我根据这个逻辑为自己写了一个函数

 df = df.repartition("id")
 df.cache()
 df.show()


 #metric = any callable function to calculate distance b/w two vectors
 def pairwise_metric(Y, metric, col_name="metric"):

     Y2 = Y.select(f.col("id").alias("id2"), 
                 f.col("features").alias("features2"))

     # join to create lower or upper half
     Y = Y.join(Y2, Y.id < Y2.id2, "inner")

     def sort_list(x):

         x = sorted(x, key=lambda y:y[0])
         x = list(map(lambda y:y[1], x))

         return(x)

     udf_diff = f.udf(lambda x,y: metric(x,y), t.FloatType())
     udf_sort = f.udf(sort_list, t.ArrayType(t.FloatType()))

     Yid = Y2.select("id2").distinct().select("id2", 
          f.col("id2").alias("id")).withColumn("dist", f.lit(0.0))

     Y = Y.withColumn("dist", udf_diff("features", 
              "features2")).drop("features","features2")

     # just swap the column names and take union to get the other half
     Y =Y.union(Y.select(f.col("id2").alias("id"),
          f.col("id").alias("id2"), "dist"))
     # union for the diagonal elements of distance matrix
     Y = Y.union(Yid)

     st1 = f.struct(["id2", "dist"]).alias("vals")
     # groupby , aggregate and sort
     Y = (Y.select("id",st1).groupBy("id").agg(f.collect_list("vals").
                             alias("vals")).withColumn("dist",udf_sort("vals")).drop("vals"))

     return(Y.select(f.col("id").alias("id1"), f.col("dist").alias(col_name)))
df=df.重新分区(“id”)
df.cache()
df.show()
#metric=计算带两个向量的距离b/w的任何可调用函数
def成对度量(Y,度量,col_name=“metric”):
Y2=Y.select(f.col(“id”)。别名(“id2”),
f、 颜色(“特征”)。别名(“特征2”))
#连接以创建下半部分或上半部分
Y=Y.join(Y2,Y.id
如果你想用欧几里德公式计算一个带有列的固定条目,只需这样做

import pyspark.sql.functions as F
from pyspark.sql.types import FloatType
from scipy.spatial import distance

fixed_entry = [0,3,2,7...] #for example, the entry against which you want distances
distance_udf = F.udf(lambda x: float(distance.euclidean(x, fixed_entry)), FloatType())
df = df.withColumn('distances', distance_udf(F.col('features')))

df将有一列距离。

下面是一个使用SQL函数power()计算两个数据帧中匹配行之间欧氏距离的实现

cols2Join = ['Key1','Key2']
colsFeature =['Feature1','Feature2','Feature3','Feature4']
columns = cols2Join + colsFeature

valuesA = [('key1value1','key2value1',111,22,33,.334),('key1value3','key2value3', 333,444,12,.445),('key1value5','key2value5',555,666,101,.99),('key1value7','key2value7',777,888,10,.019)]
table1 = spark.createDataFrame(valuesA,columns)
valuesB = [('key1value1','key2value1',22,33,3,.1),('key1value3','key2value3', 88,99,4,1.23),('key1value5','key2value5',4,44,1,.998),('key1value7','key2value7',9,99,1,.3)]
table2= spark.createDataFrame(valuesB,columns)

#Create the sql expression using list comprehension, we use sql function power to compute euclidean distance inline
beginExpr='power(('
InnerExpr = ['power((a.{}-b.{}),2)'.format(x,x) for x in colsFeature]
InnerExpr = '+'.join(str(e) for e in InnerExpr)
endExpr ='),0.5) AS EuclideanDistance'
distanceExpr = beginExpr + InnerExpr + endExpr
Expr =  cols2Join+  [distanceExpr]

#now just join the tables and use Select Expr to get Euclidean distance
outDF = table1.alias('a').join(table2.alias('b'),cols2Join,how="inner").selectExpr(Expr)

display(outDF)

对于approxSimilarityJoin,3.0是阈值参数,超过该参数将拒绝所有距离。所以它不会给出一个包含所有距离的完整数组。我实际上使用的是这个库,但当数据集太小时会出现问题。我正在进行过采样,当少数类太小(例如5)时,如果我进行BucketedRandomProjection LSH,我认为桶中只放置了一个元素,没有其他元素。这就是为什么我要寻找暴力。有什么解决办法吗?你需要一个条目和一整列之间的距离还是两列之间的距离?对于第一种情况,创建一个带有该条目的UDF并将列提供给它是非常简单的。在第二种情况下,情况变得有点复杂。@mayankagrawal这是我要找的第一种情况。我不太熟悉UDF,所以你能给我一些提示吗?这是不是类似于笛卡尔加上没有副本的事实?因为你将交叉连接数据帧本身,超过一半的成对距离计算是不必要的。文件“/usr/local/spark/python/lib/pyspark.zip/pyspark/worker.py”,第71行,返回lambda*a:f(*a)File“”,第6行,文件“/home/yhkwon/anaconda3/lib/python3.6/site packages/scipy/space/distance.py”,第433行,欧几里德距离=norm(u-v)值错误:操作数不能与形状(31,)(2,)一起广播,我正在尝试你的方法,这就是我在收集输出时得到的结果。我从数据框中为fixed_条目选择了一行。对不起,我使用了row对象而不是其中的数组。谢谢有没有办法不使用scipy库?@staticmethod我创建了自己的函数,这样就不用scipy库def euclidean(x,y):距离=0表示范围内的I(len(x)):距离+=((x[I]-y[I])*(x[I]-y[I])返回浮点(math.sqrt(distance))是的,你可以避免使用scipy。如果numpy适合您,请使用dist=numpy.linalg.norm(a-b)。如果你不想使用任何库,你的方法很好,但我怀疑它会比库快。无论如何,您可以像下面这样在一行中编写for循环math.sqrt(sum((a-b)**2表示zip(a,b)中的a,b)。看看这个