Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/apache-spark/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Scala 如何找到数据帧中某列中每个值与其他数据帧中另一列最接近的值?_Scala_Apache Spark - Fatal编程技术网

Scala 如何找到数据帧中某列中每个值与其他数据帧中另一列最接近的值?

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

我从两个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    -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")
  }
因此,我完成了代码,但有几个问题

  • 我使用了DF.transform函数而不是UDF。是更好还是一样
  • 互联网上的大多数/所有广播示例都使用类似于map的结构/json/case类。我刚刚用DF进行了广播。两者之间有什么优势/劣势吗
  • 我能改进代码吗
  • 这是一个足够好的可扩展解决方案吗?我选择使用spark,就好像数据是流式的一样,它也可以轻松地处理数据。考虑到每秒可能有数百或数千个事件,如果不使用Spark之类的流式/批处理引擎,还有什么其他可伸缩选项(在Scala中)
  • 首先,使用
    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'))
    
  • 使用此airportDF将驱动程序端的
    collect()
    收集到元组数组中,然后
    向所有执行器节点广播

  • 写入lambda或UDF以获取userDF的每条记录,并将其与数组中的每个元素进行比较,然后选择距离最小的元素,并从
    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

  • 由于性能原因,更喜欢使用DF而不是UDF。钨合金可以优化DF操作,但不能优化UDF操作
  • 这里广播很好
  • 嗯,我也不喜欢嵌套方法和变量的变异
  • Spark在这种情况下很好,但如果它是作为批处理应用程序完成的,那么您的成功还取决于集群配置和每个任务的适当资源分配

  • 您想使用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)