You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spark分析不同年份共同前往国家的人群及出行信息方案咨询

解决方案:按年份统计共同出行人群及对应国家

问题分析

你遇到的核心问题是用FPGrowth直接处理全量数据时,丢失了年份和国家这两个关键维度的关联。FPGrowth本身是用来挖掘频繁项集的,但我们需要的是同一年份下,人群组合共同前往的国家集合以及对应的次数,完全不需要用到协同过滤(协同过滤更多用于用户-物品的推荐场景)。更合适的思路是先按年份拆分数据,再针对每个年份内的人群-国家关系做关联分析,既可以直接生成所有有效人群组合,也可以结合FPGrowth挖掘高频人群组合后匹配对应国家信息。

具体实现步骤

我们可以通过三步完成需求,先聚合基础数据,再生成人群组合,最后计算共同国家和次数:

  1. 按年份+人群分组,得到每个人在对应年份去过的国家集合
  2. 按年份生成该年份内所有2人及以上的非重复人群组合
  3. 计算每个组合的共同国家集合,次数即为共同国家的数量

下面是完整的Scala代码实现:

val spark = SparkSession.builder().master("local[1,1]").config("spark.sql.shuffle.partitions", "1").config("spark.sql.crossJoin.enabled","true").getOrCreate()
spark.sparkContext.setLogLevel("error")
import spark.implicits._
import org.apache.spark.sql.functions._

// 加载原始数据
val df=Seq(
 ("tom","America","2019"),
 ("jim","America","2019"),
 ("jack","America","2019"),
 ("tom","Russia","2019"),
 ("jim","Russia","2019"),
 ("jack","Russia","2019"),
 ("alex","Russia","2019"),
 ("tom","America","2018"),
 ("jim","America","2018"),
 ("tom","Germany","2018"),
 ("jim","England","2018")
).toDF("person","country","year")

// 步骤1:按年份和人分组,得到每个人在对应年份的国家集合
val personCountryByYear = df.groupBy("year", "person")
  .agg(collect_set("country").alias("countries"))
  .orderBy("year", "person")

// 步骤2:定义UDF处理单年份内的人群组合与共同国家计算
def processYearGroup(personCountryList: Seq[(String, Seq[String])]): Seq[(Seq[String], Seq[String], Int)] = {
  // 生成所有2人及以上的非重复组合
  val allValidCombinations = (2 to personCountryList.size).flatMap { groupSize =>
    personCountryList.combinations(groupSize).map(_.map(_._1))
  }
  // 对每个组合计算共同国家与次数
  allValidCombinations.map { persons =>
    val countrySets = persons.map(p => personCountryList.find(_._1 == p).get._2.toSet)
    val commonCountries = countrySets.reduce(_ intersect _).toSeq.sorted
    val times = commonCountries.size
    (persons.sorted, commonCountries, times)
  }.filter(_._3 > 0) // 过滤无共同国家的无效组合
}

val processYearUDF = udf(processYearGroup _)

// 步骤3:应用UDF并整理成目标格式
val result = personCountryByYear.groupBy("year")
  .agg(collect_list(struct("person", "countries")).alias("person_country_list"))
  .withColumn("items", processYearUDF(col("person_country_list")))
  .select("year", "items")
  .withColumn("items", explode(col("items")))
  .select(
    col("year"),
    col("items._1").alias("persons"),
    col("items._2").alias("common"),
    col("items._3").alias("times")
  )
  .groupBy("year")
  .agg(collect_list(struct("persons", "common", "times")).alias("items"))
  .orderBy("year")

// 输出为目标JSON格式
result.toJSON.show(false)

针对大数据量的优化(用FPGrowth挖掘高频人群组合)

如果你的数据量很大,不需要所有人群组合,只关注频繁共同出行的人群,可以在每个年份内用FPGrowth挖掘频繁项集,再匹配共同国家:

import org.apache.spark.ml.fpm.FPGrowth

// 按年份拆分数据
val yearList = personCountryByYear.select("year").distinct().collect().map(_.getString(0))

val finalResultDFs = yearList.map { year =>
  // 生成当前年份的人群列表,适配FPGrowth输入格式
  val transactionDF = personCountryByYear.filter(col("year") === year)
    .select("person").map(row => Array(row.getString(0))).toDF("items")
  
  // 初始化FPGrowth,调整minSupport控制频繁项集的阈值
  val fpgrowth = new FPGrowth().setItemsCol("items").setMinSupport(0.1)
  val model = fpgrowth.fit(transactionDF)
  
  // 筛选长度>=2的频繁人群组合
  val frequentGroups = model.freqItemsets.filter(col("items").size >= 2)
    .select("items").collect().map(_.getAs[Seq[String]](0))
  
  // 构建当前年份的人群-国家映射
  val personCountryMap = personCountryByYear.filter(col("year") === year)
    .rdd.map(row => (row.getString(0), row.getAs[Seq[String]](1))).collectAsMap()
  
  // 计算每个高频组合的共同国家与次数
  val items = frequentGroups.map { persons =>
    val countrySets = persons.map(p => personCountryMap(p).toSet)
    val commonCountries = countrySets.reduce(_ intersect _).toSeq.sorted
    val times = commonCountries.size
    (persons.sorted, commonCountries, times)
  }.filter(_._3 > 0)
  
  // 转换为DataFrame并标记年份
  spark.createDataFrame(items).toDF("persons", "common", "times")
    .withColumn("year", lit(year))
}

// 合并所有年份结果并整理格式
val finalResult = finalResultDFs.reduce(_ union _)
  .groupBy("year")
  .agg(collect_list(struct("persons", "common", "times")).alias("items"))
  .orderBy("year")

finalResult.toJSON.show(false)

内容的提问来源于stack exchange,提问作者eascape

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:34:41