Scala 如何找到数据帧中某列中每个值与其他数据帧中另一列最接近的值?
我从两个csv文件中读取了两个数据帧 airportDF 150kb 7000记录Scala 如何找到数据帧中某列中每个值与其他数据帧中另一列最接近的值?,scala,apache-spark,Scala,Apache Spark,我从两个csv文件中读取了两个数据帧 airportDF 150kb 7000记录 iata_code latitude longitude AAA -17.352606 -145.509956 AAB -26.69317 141.0478 AAC 31.07333 33.83583 用户数据量~75MB~100万条记录 uuid geoip_latitude geoip_longitude DDEFEBEA-98ED-49EB-A4E7-9D7BFDB7AA0B
iata_code latitude longitude
AAA -17.352606 -145.509956
AAB -26.69317 141.0478
AAC 31.07333 33.83583
用户数据量~75MB~100万条记录
uuid geoip_latitude geoip_longitude
DDEFEBEA-98ED-49EB-A4E7-9D7BFDB7AA0B -37.8333015441895 145.050003051758
DAEF2221-14BE-467B-894A-F101CDCC38E4 52.5167007446289 4.66669988632202
31971B3E-2F80-4F8D-86BA-1F2077DF36A2 35.685001373291 139.751403808594
我想根据地理距离找到离用户最近的机场
输出应有两列UUID和相应的iata\U代码
我有哈弗森效用函数来计算地理距离
def distance(
startLon: Double,
startLat: Double,
endLon: Double,
endLat: Double,
R: Double
): Double = {
val dLat = math.toRadians(endLat - startLat)
val dLon = math.toRadians(endLon - startLon)
val lat1 = math.toRadians(startLat)
val lat2 = math.toRadians(endLat)
val a =
math.sin(dLat / 2) * math.sin(dLat / 2) +
math.sin(dLon / 2) * math.sin(dLon / 2) * math.cos(lat1) * math.cos(lat2)
val c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
R * c
}
编辑:
userDF
|-- uuid: string (nullable = true)
|-- geoip_latitude: double (nullable = true)
|-- geoip_longitude: double (nullable = true)
airportDF
|-- iata_code: string (nullable = true)
|-- latitude: double (nullable = true)
|-- longitude: double (nullable = true)
transformations(spark, userDF, airportDF).show()
def transformations(spark: SparkSession, userDF: DataFrame, airportDF: DataFrame) = {
val airports = broadcastDF(spark, airportDF)
userDF.transform(findNearestAirport(spark, airports.value))
}
def broadcastDF(spark: SparkSession, df: DataFrame) = {
spark.sparkContext.broadcast(df.collect())
}
def findNearestAirport(spark: SparkSession, airports: Array[Row])(
userDF: DataFrame
): DataFrame = {
import spark.implicits._
var distance = Double.MaxValue
var minDistance = Double.MaxValue
var nearestAirportID = ""
userDF.flatMap { user =>
airports.foreach { airport =>
distance = Haversine.distance(
user.getAs[Double]("geoip_longitude"),
user.getAs[Double]("geoip_latitude"),
airport.getAs[Double]("longitude"),
airport.getAs[Double]("latitude")
)
if (minDistance > distance) {
minDistance = distance
nearestAirportID = airport.getAs[String]("iata_code")
}
}
println(s"User ${user.getAs[String]("uuid")} is closest to airport $nearestAirportID")
Seq((user.getAs[String]("uuid"), nearestAirportID))
}.toDF("uuid", "iata_code")
}
因此,我完成了代码,但有几个问题
struct
import org.apache.spark.sql.functions.struct
airportDF.withColumn("uuid_lat_long_struct", struct(airportDF("uuid"),airportDF("geoip_lat"), airportDF("geoip_long"))
collect\u set
将airportDF中的每一行折叠为一个数组/列表,并创建一个新的airportflattedf
数据帧。例如
airportDF.groupBy('uuid').agg(collect_list('uuid_lat_long_struct'))
collect()
收集到元组数组中,然后向所有执行器节点广播
airportplattedf
平地
公式,这将减少三角函数的数量,因为您只关心相对距离
因为您已经有了计算距离的函数,所以我将使用dataset API,然后做一个平面图
case class User(id: String, lat: Double, longitude: Double)
case class Airport(id: String, lat: Double, longitude: Double)
val users = usersDf.as[User]
val airports = spark.sparkContext.broadcast(airports.as[Airport])
val output= users.flatMap { user=>
airports.value.flatMap { airport=>
val dist = distance(airport.longitude,airport.lat,user.longitude,user.lat,1.0) //was not sure what R is
if(dist < 10.0) Seq((user.id,airport.id,dist))
else Seq.empty
}
}
case类用户(id:String,lat:Double,longitude:Double)
案例类机场(id:String,纬度:Double,经度:Double)
val users=usersDf.as[User]
val airports=spark.sparkContext.broadcast(airports.as[Airport])
val output=users.flatMap{user=>
airports.value.flatMap{airport=>
val dist=距离(airport.longitude,airport.lat,user.longitude,user.lat,1.0)//不确定R是什么
if(距离<10.0)序列((用户id、机场id、距离))
否则为空
}
}
由于没有连接,如何获得所需格式的输出
您还可以定义自定义类并相应地填充字段
尽管需要注意:当您使用spark数据集API时,您会错过spark所做的一些优化。如果您有大量与地理空间面积相关的计算,请仔细查看。它可以让你轻松摆脱UDF和grouBy的负担。例如: 在您的示例中,您只需要最近的点,因此将K=1或尝试找到返回最近点的方法(不确定它是否存在)。 如果您不喜欢使用第三方libs,只需使用UDFs+groupby或一些窗口函数即可 UPD
您想使用dataframe api实现这一点,还是愿意使用dataset api?我也可以使用dataset。您能删除图像并以文本形式添加数据吗?因此,如果有人想快速复制并测试它,这将很容易。@koiralo当然,完成了!我只是想大声说出来,但是你能收集机场数据作为地图并广播它,并在平面地图操作中计算用户与所有机场之间的距离吗?我认为你的回答中有一些错误/误解。1) 是句法上的。2) 当不能分组时,为什么要分组。所有UUID都是唯一的?3) 当没有字段/谓词连接时,它将如何连接?1)不确定您所指的是什么。这个函数使用名为
struct
的函数连接两列。这是需要的,因为我们需要lat/long来执行计算。2) groupby是这样做的,我们可以使用收集列表
进行透视。这将为您提供与机场的每个元素(行)进行比较的数组。3)是的,我看到它没有要加入的列-我是说广播变量。将更正选项Htthanks以获得答案。我更新并发布了一些其他问题,请回答。另外,我没有使用struct进行合并,因为模式是嵌套的,很难读取,没有它也可以正常工作。并且没有同时使用lambda和UDF。平坦地球的想法听起来真的很好,我以后再看。基本上我们做了一个嵌套循环。除了广播还有别的办法吗?我过去用过,效率不高。还有,我如何找到最接近scala/spark的方式。广播不会成为你的瓶颈,你正在广播150KB,这几乎算不上什么。您可以将每一行用户与机场中的每一行连接起来,但行数将爆炸式增长,我预计性能会更差。除非您能够将机场数据预过滤为用户数据,否则您将始终需要进行1M*7000计算。谢谢!你的回答很有帮助。是的,我也认为除了m*n或者kd树什么的,没有别的办法了。我尝试使用Dataset,但有很多编码器错误,必须再次更改为DF。s
val geometryFactory = new GeometryFactory()
val pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
val K = 1000 // K Nearest Neighbors
val usingIndex = false
val result = KNNQuery.SpatialKnnQuery(objectRDD, pointObject, K, usingIndex)