Scala - 如何从原始数据创建映射以计算唯一值

3

我是一位新手,正在尝试读取输入的原始数据,以便在多个字段上使用groupBy生成映射。

样本原始数据:

date,uid,site,success
2014-07-14,userA,google,1
2014-07-14,userB,google,1
2014-07-14,userC,yahoo,1
2014-07-14,userD,facebook,1

我希望报告每个日期每个站点的不同用户数量,例如:

2014-07-14,google,2
2014-07-14,yahoo,1
2014-07-14,facebook,1

为此,我试图使用按日期和站点字段分组,并将值设置为uid的groupBy方法。一旦我有了这个数据结构,我可以遍历映射并计算不同的映射值。 请问有人能告诉我如何生成这个数据结构吗? 谢谢!

  1. 你的输入数据有多大?
  2. 你以后需要跨日期计算不同用户的数量吗?
- Shyamendra Solanki
每天大约有一百万行数据,需要跨日期计算不同用户的数量。谢谢! - user2727704
3个回答

2

我希望我理解的是正确的。这里有一个完整的例子。

case class Data(date: String, uid: String, site: String, success: Int)

val sampleData = List(
  Data("2014-07-14","userA","google",1),
  Data("2014-07-14","userA","google",1),
  Data("2014-07-14","userB","google",1),
  Data("2014-07-14","userC","yahoo",1),
  Data("2014-07-14","userD","facebook",1)
)

sampleData.groupBy(_.date).map
  {case (date, datelist) => (date, datelist.groupBy(_.site).map
    {case (site, sitelist) => (site, sitelist.groupBy(_.uid).size)})}

输出结果为:Map(2014-07-14 -> Map(google -> 2, yahoo -> 1, facebook -> 1)) 基本上,您会得到每个日期的一个地图,其中包含来自不同用户的网站访问。请注意,来自userA的2个访问计为1个。
 sitelist.groupBy(_.uid).size

计算不同uid的访问次数。

编辑 是的,可以在不使用额外数据结构的情况下实现。现在你只需要处理数组的索引。

val fileText = """2014-07-14,userA,google,1
  2014-07-14,userA,google,1
  2014-07-14,userA,google,1
  2014-07-14,userB,google,1
  2014-07-14,userC,yahoo,1
  2014-07-14,userD,facebook,1""".stripMargin

fileText.lines.map(_.split(",")).toList.groupBy(_(0)).map
  {case (date, datelist) => (date, datelist.groupBy(_(2)).map
    {case (site, sitelist) => (site, sitelist.groupBy(_(1)).size)})}

谢谢Kigyo,这确实回答了我的问题。由于我将解析外部文件的原始数据,构造一个Data对象是否会增加额外的开销?还有其他的选择吗? - user2727704

1
为了清晰起见,可以忽略标题行,可能的实现如下:
val text = """2014-07-14,userA,google,1
            |2014-07-14,userA,google,1
            |2014-07-14,userB,google,1
            |2014-07-14,userC,yahoo,1
            |2014-07-16,userC,yahoo,1
            |2014-07-14,userD,facebook,1
            |2014-07-14,userE,facebook,1
            |""".stripMargin

val uniqueUsersByDateSite: Map[(String, String), Int] = text.lines.map {
  line =>
    val tokens = line.split(",")
    (tokens(0), tokens(1), tokens(2))
}.toSet.groupBy {
  tuple: (String, String, String) =>
    (tuple._1, tuple._3)
}.mapValues {
  _.size
}

通过创建一个元组集合 (date, uid, site),我们为每个特定日期和网站上的唯一用户收集一个项目。
然后,groupBy 方法按 (date, site) 进行分组,将相同日期和网站的 N 个项目转换为一个映射条目,其中包含对应日期和网站的唯一用户数的项目数量。
最终的 mapValue 方法实现了所需的结果:
Map((2014-07-16,yahoo) -> 1, (2014-07-14,facebook) -> 2, (2014-07-14,google) -> 2, (2014-07-14,yahoo) -> 1)

非常感谢你,Luigi。从你的回答中我学到了一些有关Scala的新知识。 - user2727704

0

我觉得@Kigyo回答的很好,但是我认为你可以稍微扩展一下: 所以,假设有这样一个数据结构:

case class Data(date: String, uid: String, site: String, success: Int)
val sampleData = List(
  Data("2014-07-14","userA","google",1),
  Data("2014-07-14","userA","google",1),
  Data("2014-07-14","userB","google",1),
  Data("2014-07-14","userC","yahoo",1),
  Data("2014-07-14","userD","facebook",1)
)

你可以通过以下方式实现你想要的:

list.groupBy((_.date , _.site)).collect{ case (a , b : List[Data]) =>(a._1 , a._2 , b.map(_.success).sum) } ;

它返回一个Tuple3列表,就像你想要的一样


是的,这取决于@user2727704想要什么类型的数据结构作为结果。我还假设success的数量是无关紧要的,因为不同用户的数量与此无关。 - Kigyo
有没有其他方法可以对uid本身应用count distinct,因为我可能并不总是拥有success字段,而不是使用b.map(_.success).sum?谢谢! - user2727704
如果您希望“success”字段像日期和站点一样被使用(作为过滤器),最好的方法是将其包含在第一个元组中(在分组中)。 - fahim ayat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接