{"action":"created","comment":{"url":"https://api.github.com/repos/Azure/mmlspark/pulls/comments/229353128","pull_request_review_id":169828291,"id":229353128,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyOTM1MzEyOA==","diff_hunk":"@@ -0,0 +1,382 @@\n+// Copyright (C) Microsoft Corporation. All rights reserved.\n+// Licensed under the MIT License. See LICENSE in project root for information.\n+\n+package com.microsoft.ml.spark\n+\n+import org.apache.spark.ml.param._\n+import org.apache.spark.ml.recommendation._\n+import org.apache.spark.ml.util._\n+import org.apache.spark.ml.{Estimator, Model, Transformer}\n+import org.apache.spark.sql._\n+import org.apache.spark.sql.expressions.Window\n+import org.apache.spark.sql.functions._\n+import org.apache.spark.sql.functions.{collect_list, rank => r}\n+import org.apache.spark.sql.types.{ArrayType, FloatType, IntegerType, StructType}\n+\n+import scala.collection.mutable\n+import scala.util.Random\n+\n+trait RankingParams extends Params {\n+  val minRatingsPerUser: IntParam = new IntParam(this,\n+    \"minRatingsPerUser\",\n+    \"min ratings for users > 0\",\n+    ParamValidators.inRange(0, Integer.MAX_VALUE))\n+\n+  val minRatingsPerItem: IntParam = new IntParam(this,\n+    \"minRatingsPerItem\",\n+    \"min ratings for items > 0\",\n+    ParamValidators.inRange(0, Integer.MAX_VALUE))\n+\n+  /** @group setParam */\n+  def setMinRatingsPerUser(value: Int): this.type = set(minRatingsPerUser, value)\n+\n+  /** @group setParam */\n+  def setMinRatingsPerItem(value: Int): this.type = set(minRatingsPerItem, value)\n+\n+  /** @group getParam */\n+  def getMinRatingsPerUser: Int = $(minRatingsPerUser)\n+\n+  /** @group getParam */\n+  def getMinRatingsPerItem: Int = $(minRatingsPerItem)\n+\n+  val recommender = new EstimatorParam(this, \"recommender\", \"estimator for selection\", { x: Estimator[_] => true })\n+\n+  /** @group getParam */\n+  def getRecommender: Estimator[_ <: Model[_]] = $(recommender)\n+\n+  /** @group setParam */\n+  def setRecommender(value: Estimator[_ <: Model[_]]): this.type = set(recommender, value)\n+\n+  val k: IntParam = new IntParam(this, \"k\", \"number of items to recommend\")\n+\n+  /** @group getParam */\n+  def getK: Int = $(k)\n+\n+  /** @group setParam */\n+  def setK(value: Int): this.type = set(k, value)\n+\n+}\n+\n+trait RankingFunctions extends RankingParams with HasRecommenderCols {\n+\n+  private def filterByItemCount(dataset: Dataset[_], itemCol: String, userCol: String): DataFrame =\n+    dataset\n+      .groupBy(userCol)\n+      .agg(col(userCol), count(col(itemCol)).alias(\"nitems\"))\n+      .where(col(\"nitems\") >= $(minRatingsPerUser))\n+      .drop(\"nitems\")\n+      .cache()\n+\n+  private def filterByUserRatingCount(dataset: Dataset[_], itemCol: String, userCol: String): DataFrame =\n+    dataset\n+      .groupBy(itemCol)\n+      .agg(col(itemCol), count(col(userCol)).alias(\"nusers\"))\n+      .where(col(\"nusers\") >= $(minRatingsPerItem))\n+      .join(dataset, itemCol)\n+      .drop(\"nusers\")\n+      .cache()\n+\n+  def filterRatings(dataset: Dataset[_], itemCol: String, userCol: String): DataFrame =\n+    filterByUserRatingCount(dataset, itemCol, userCol)\n+      .join(filterByItemCount(dataset, itemCol, userCol), userCol)\n+\n+  def prepareTestData(userColumn: String, itemColumn: String,\n+    validationDataset: DataFrame, recs: DataFrame,\n+    k: Int): Dataset[_] = {\n+    import org.apache.spark.sql.functions.{collect_list, rank => r}\n+\n+    val perUserRecommendedItemsDF: DataFrame = recs\n+      .select(userColumn, \"recommendations.\" + itemColumn)\n+      .withColumnRenamed(itemColumn, \"prediction\")\n+\n+    val perUserActualItemsDF = if (validationDataset.columns.contains($(ratingCol))) {\n+      val windowSpec = Window.partitionBy(userColumn).orderBy(col($(ratingCol)).desc)\n+\n+      validationDataset\n+        .select(userColumn, itemColumn, $(ratingCol))\n+        .withColumn(\"rank\", r().over(windowSpec).alias(\"rank\"))\n+        .where(col(\"rank\") <= k)\n+        .groupBy(userColumn)\n+        .agg(col(userColumn), collect_list(col(itemColumn)))\n+        .withColumnRenamed(\"collect_list(\" + itemColumn + \")\", \"label\")\n+        .select(userColumn, \"label\")\n+    } else {\n+      val windowSpec = Window.partitionBy(userColumn).orderBy(col($(itemCol)).desc)\n+\n+      validationDataset\n+        .select(userColumn, itemColumn)\n+        .withColumn(\"rank\", r().over(windowSpec).alias(\"rank\"))\n+        .where(col(\"rank\") <= k)\n+        .groupBy(userColumn)\n+        .agg(col(userColumn), collect_list(col(itemColumn)))\n+        .withColumnRenamed(\"collect_list(\" + itemColumn + \")\", \"label\")\n+        .select(userColumn, \"label\")\n+    }\n+    val joined_rec_actual = perUserRecommendedItemsDF\n+      .join(perUserActualItemsDF, userColumn)\n+      .drop(userColumn)\n+\n+    joined_rec_actual\n+  }\n+\n+  def split(dataframe: Dataset[_], trainRatio: Double, itemCol: String, userCol: String, ratingCol: String):\n+  (DataFrame, DataFrame) = {\n+    val dataset = filterRatings(dataframe.dropDuplicates(), itemCol, userCol)\n+\n+    if (dataset.columns.contains(ratingCol)) {\n+      val wrapColumn = udf((itemId: Double, rating: Double) => Array(itemId, rating))\n+\n+      val sliceudf = udf(\n+        (r: mutable.WrappedArray[Array[Double]]) => r.slice(0, math.round(r.length * trainRatio)\n+          .toInt))\n+      val shuffle = udf((r: mutable.WrappedArray[Array[Double]]) => Random.shuffle(r.toSeq))\n+      val dropudf = udf((r: mutable.WrappedArray[Array[Double]]) => r.drop(math.round(r.length * trainRatio).toInt))\n+\n+      val testds = dataset\n+        .withColumn(\"itemIDRating\", wrapColumn(col(itemCol), col(ratingCol)))\n+        .groupBy(col(userCol))\n+        .agg(collect_list(col(\"itemIDRating\")))\n+        .withColumn(\"shuffle\", shuffle(col(\"collect_list(itemIDRating)\")))\n+        .withColumn(\"train\", sliceudf(col(\"shuffle\")))\n+        .withColumn(\"test\", dropudf(col(\"shuffle\")))\n+        .drop(col(\"collect_list(itemIDRating)\")).drop(col(\"shuffle\"))\n+        .cache()\n+\n+      val popLeft = udf((r: mutable.WrappedArray[Double]) => r(0))\n+      val popRight = udf((r: mutable.WrappedArray[Double]) => r(1))\n+\n+      val train = testds\n+        .select(userCol, \"train\")\n+        .withColumn(\"itemIdRating\", explode(col(\"train\")))\n+        .drop(\"train\")\n+        .withColumn(itemCol, popLeft(col(\"itemIdRating\")))\n+        .withColumn(ratingCol, popRight(col(\"itemIdRating\")))\n+        .drop(\"itemIdRating\")\n+\n+      val test = testds\n+        .select(userCol, \"test\")\n+        .withColumn(\"itemIdRating\", explode(col(\"test\")))\n+        .drop(\"test\")\n+        .withColumn(itemCol, popLeft(col(\"itemIdRating\")))\n+        .withColumn(ratingCol, popRight(col(\"itemIdRating\")))\n+        .drop(\"itemIdRating\")\n+\n+      (train, test)\n+    } else {\n+      val shuffle = udf((r: mutable.WrappedArray[Double]) => Random.shuffle(r.toSeq))\n+      val sliceudf = udf((r: mutable.WrappedArray[Double]) => r.slice(0, math.round(r.length * trainRatio).toInt))\n+      val dropudf = udf((r: mutable.WrappedArray[Double]) => r.drop(math.round(r.length * trainRatio).toInt))\n+\n+      val testds = dataset\n+        .groupBy(col(userCol))\n+        .agg(collect_list(col(itemCol)))\n+        .withColumn(\"shuffle\", shuffle(col(\"collect_list(\" + itemCol + \")\")))\n+        .withColumn(\"train\", sliceudf(col(\"shuffle\")))\n+        .withColumn(\"test\", dropudf(col(\"shuffle\")))\n+        .drop(col(\"collect_list(\" + itemCol + \")\")).drop(col(\"shuffle\"))\n+        .cache()\n+\n+      val train = testds\n+        .select(userCol, \"train\")\n+        .withColumn(itemCol, explode(col(\"train\")))\n+        .drop(\"train\")\n+\n+      val test = testds\n+        .select(userCol, \"test\")\n+        .withColumn(itemCol, explode(col(\"test\")))\n+        .drop(\"test\")\n+\n+      (train, test)\n+    }\n+  }\n+\n+  def prepareTestData(validationDataset: DataFrame, recs: DataFrame, k: Int): Dataset[_] = {\n+    val perUserRecommendedItemsDF: DataFrame = recs\n+      .select(getUserCol, \"recommendations.\" + getItemCol)\n+      .withColumnRenamed(getItemCol, \"prediction\")\n+\n+    val perUserActualItemsDF = if (validationDataset.columns.contains(getRatingCol)) {\n+      val windowSpec = Window.partitionBy(getUserCol).orderBy(col(getRatingCol).desc)\n+\n+      validationDataset\n+        .select(getUserCol, getItemCol, getRatingCol)\n+        .withColumn(\"rank\", r().over(windowSpec).alias(\"rank\"))\n+        .where(col(\"rank\") <= k)\n+        .groupBy(getUserCol)\n+        .agg(col(getUserCol), collect_list(col(getItemCol)))\n+        .withColumnRenamed(\"collect_list(\" + getItemCol + \")\", \"label\")\n+        .select(getUserCol, \"label\")\n+    } else {\n+      val windowSpec = Window.partitionBy(getUserCol).orderBy(col(getItemCol).desc)\n+\n+      validationDataset\n+        .select(getUserCol, getItemCol)\n+        .withColumn(\"rank\", r().over(windowSpec).alias(\"rank\"))\n+        .where(col(\"rank\") <= k)\n+        .groupBy(getUserCol)\n+        .agg(col(getUserCol), collect_list(col(getItemCol)))\n+        .withColumnRenamed(\"collect_list(\" + getItemCol + \")\", \"label\")\n+        .select(getUserCol, \"label\")\n+    }\n+    val joined_rec_actual = perUserRecommendedItemsDF\n+      .join(perUserActualItemsDF, getUserCol)\n+      .drop(getUserCol)\n+\n+    joined_rec_actual\n+  }\n+}\n+\n+class RankingAdapter(override val uid: String)\n+  extends Estimator[RankingAdapterModel] with ComplexParamsWritable with RankingFunctions {\n+\n+  def this() = this(Identifiable.randomUID(\"RecommenderAdapter\"))\n+\n+  /** @group getParam */\n+  override def getUserCol: String = getRecommender.asInstanceOf[Estimator[_] with PublicALSParams].getUserCol\n+\n+  /** @group getParam */\n+  override def getItemCol: String = getRecommender.asInstanceOf[Estimator[_] with PublicALSParams].getItemCol\n+\n+  /** @group getParam */\n+  override def getRatingCol: String = getRecommender.asInstanceOf[Estimator[_] with PublicALSParams].getRatingCol\n+\n+  val mode: Param[String] = new Param(this, \"mode\", \"recommendation mode\")\n+\n+  /** @group getParam */\n+  def getMode: String = $(mode)\n+\n+  /** @group setParam */\n+  def setMode(value: String): this.type = set(mode, value)\n+\n+  setDefault(mode -> \"allUsers\", k -> 10)\n+\n+  def transformSchema(schema: StructType): StructType = {\n+    val model = getRecommender.asInstanceOf[ALS]\n+    getMode match {\n+      case \"allUsers\" => new StructType()\n+        .add(\"userCol\", IntegerType)\n+        .add(\"recommendations\", ArrayType(\n+          new StructType().add(\"itemCol\", IntegerType).add(\"rating\", FloatType))\n+        )\n+      case \"allItems\" => new StructType()\n+        .add(\"itemCol\", IntegerType)\n+        .add(\"recommendations\", ArrayType(\n+          new StructType().add(\"userCol\", IntegerType).add(\"rating\", FloatType))\n+        )\n+      case \"normal\"   => new StructType()\n+        .add(\"userCol\", IntegerType)\n+        .add(\"recommendations\", ArrayType(\n+          new StructType().add(\"itemCol\", IntegerType).add(\"rating\", FloatType))\n+        )\n+    }\n+  }\n+\n+  def fit(dataset: Dataset[_]): RankingAdapterModel = {\n+    new RankingAdapterModel()\n+      .setRecommenderModel(getRecommender.fit(dataset))\n+      .setMode(getMode)\n+      .setNItems(getK)\n+      .setUserCol(getUserCol)\n+      .setItemCol(getItemCol)\n+      .setRatingCol(getRatingCol)\n+  }\n+\n+  override def copy(extra: ParamMap): RankingAdapter = {\n+    defaultCopy(extra)\n+  }\n+\n+}\n+\n+object RankingAdapter extends ComplexParamsReadable[RankingAdapter]\n+\n+/**\n+  * Model from train validation split.\n+  *\n+  * @param uid Id.\n+  */\n+class RankingAdapterModel private[ml](val uid: String)\n+  extends Model[RankingAdapterModel] with ComplexParamsWritable with Wrappable with RankingFunctions {\n+\n+  def recommendForAllUsers(i: Int): DataFrame = getRecommenderModel.asInstanceOf[ALSModel].recommendForAllUsers(3)\n+\n+  def recommendForAllItems(i: Int): DataFrame = getRecommenderModel.asInstanceOf[ALSModel].recommendForAllItems(3)\n+\n+  def this() = this(Identifiable.randomUID(\"RankingAdapterModel\"))\n+\n+  val recommenderModel = new TransformerParam(this, \"recommenderModel\", \"recommenderModel\", { x: Transformer => true })\n+\n+  def setRecommenderModel(m: Model[_]): this.type = set(recommenderModel, m)\n+\n+  def getRecommenderModel: Model[_] = $(recommenderModel).asInstanceOf[Model[_]]\n+\n+  val mode: Param[String] = new Param(this, \"mode\", \"recommendation mode\")\n+\n+  /** @group getParam */\n+  def getMode: String = $(mode)\n+\n+  /** @group setParam */\n+  def setMode(value: String): this.type = set(mode, value)\n+\n+  val nItems: IntParam = new IntParam(this, \"nItems\", \"recommendation mode\")\n+\n+  /** @group getParam */\n+  def getNItems: Int = $(nItems)\n+\n+  /** @group setParam */\n+  def setNItems(value: Int): this.type = set(nItems, value)\n+\n+  val nUsers: IntParam = new IntParam(this, \"nUsers\", \"recommendation mode\")\n+\n+  /** @group getParam */\n+  def getNUsers: Int = $(nUsers)\n+\n+  /** @group setParam */\n+  def setNUsers(value: Int): this.type = set(nUsers, value)\n+\n+  def transform(dataset: Dataset[_]): DataFrame = {\n+    transformSchema(dataset.schema)\n+    val model = getRecommenderModel\n+    getMode match {\n+      case \"allUsers\" => {\n+        val recs = this.recommendForAllUsers(getNItems)\n+        prepareTestData(getUserCol, getItemCol, dataset.toDF(), recs, 10).toDF()\n+      }\n+      case \"allItems\" => {\n+        val recs = model.asInstanceOf[ALSModel].recommendForAllItems(getNUsers)\n+        prepareTestData(getItemCol, getUserCol, dataset.toDF(), recs, 10).toDF()\n+      }\n+      case \"normal\"   => {\n+        val recs = SparkHelper.flatten(model.transform(dataset), 10, getItemCol, getUserCol)\n+        prepareTestData(getItemCol, getUserCol, dataset.toDF(), recs, 10).toDF()\n+      }\n+    }\n+  }\n+\n+  def transformSchema(schema: StructType): StructType = {\n+    val model = getRecommenderModel\n+    getMode match {\n+      case \"allUsers\" => new StructType()\n+        .add(\"userCol\", IntegerType)\n+        .add(\"recommendations\", ArrayType(\n+          new StructType().add(\"itemCol\", IntegerType).add(\"rating\", FloatType))\n+        )\n+      case \"allItems\" => new StructType()\n+        .add(\"itemCol\", IntegerType)\n+        .add(\"recommendations\", ArrayType(\n+          new StructType().add(\"userCol\", IntegerType).add(\"rating\", FloatType))\n+        )\n+      case \"normal\"   => new StructType()\n+        .add(\"userCol\", IntegerType)","path":"src/recommendation/src/main/scala/RankingAdapter.scala","position":369,"original_position":369,"commit_id":"7f6324c3dae5fcbef556a838ee5317221f89092f","original_commit_id":"7f6324c3dae5fcbef556a838ee5317221f89092f","user":{"login":"imatiach-msft","id":24683184,"node_id":"MDQ6VXNlcjI0NjgzMTg0","avatar_url":"https://avatars0.githubusercontent.com/u/24683184?v=4","gravatar_id":"","url":"https://api.github.com/users/imatiach-msft","html_url":"https://github.com/imatiach-msft","followers_url":"https://api.github.com/users/imatiach-msft/followers","following_url":"https://api.github.com/users/imatiach-msft/following{/other_user}","gists_url":"https://api.github.com/users/imatiach-msft/gists{/gist_id}","starred_url":"https://api.github.com/users/imatiach-msft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/imatiach-msft/subscriptions","organizations_url":"https://api.github.com/users/imatiach-msft/orgs","repos_url":"https://api.github.com/users/imatiach-msft/repos","events_url":"https://api.github.com/users/imatiach-msft/events{/privacy}","received_events_url":"https://api.github.com/users/imatiach-msft/received_events","type":"User","site_admin":false},"body":"hmm, there are a lot of hard-coded string here, I think some of these should be constants, others params?","created_at":"2018-10-30T15:21:27Z","updated_at":"2018-10-30T15:26:27Z","html_url":"https://github.com/Azure/mmlspark/pull/415#discussion_r229353128","pull_request_url":"https://api.github.com/repos/Azure/mmlspark/pulls/415","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/Azure/mmlspark/pulls/comments/229353128"},"html":{"href":"https://github.com/Azure/mmlspark/pull/415#discussion_r229353128"},"pull_request":{"href":"https://api.github.com/repos/Azure/mmlspark/pulls/415"}}},"pull_request":{"url":"https://api.github.com/repos/Azure/mmlspark/pulls/415","id":226758600,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI2NzU4NjAw","html_url":"https://github.com/Azure/mmlspark/pull/415","diff_url":"https://github.com/Azure/mmlspark/pull/415.diff","patch_url":"https://github.com/Azure/mmlspark/pull/415.patch","issue_url":"https://api.github.com/repos/Azure/mmlspark/issues/415","number":415,"state":"open","locked":false,"title":"Ranking adapater","user":{"login":"dciborow","id":9027725,"node_id":"MDQ6VXNlcjkwMjc3MjU=","avatar_url":"https://avatars0.githubusercontent.com/u/9027725?v=4","gravatar_id":"","url":"https://api.github.com/users/dciborow","html_url":"https://github.com/dciborow","followers_url":"https://api.github.com/users/dciborow/followers","following_url":"https://api.github.com/users/dciborow/following{/other_user}","gists_url":"https://api.github.com/users/dciborow/gists{/gist_id}","starred_url":"https://api.github.com/users/dciborow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dciborow/subscriptions","organizations_url":"https://api.github.com/users/dciborow/orgs","repos_url":"https://api.github.com/users/dciborow/repos","events_url":"https://api.github.com/users/dciborow/events{/privacy}","received_events_url":"https://api.github.com/users/dciborow/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-10-30T00:48:41Z","updated_at":"2018-10-30T15:26:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"91ff13b8d97a5b26de1c5c6c989cd831b652bd44","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/Azure/mmlspark/pulls/415/commits","review_comments_url":"https://api.github.com/repos/Azure/mmlspark/pulls/415/comments","review_comment_url":"https://api.github.com/repos/Azure/mmlspark/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Azure/mmlspark/issues/415/comments","statuses_url":"https://api.github.com/repos/Azure/mmlspark/statuses/7f6324c3dae5fcbef556a838ee5317221f89092f","head":{"label":"dciborow:rankingAdapater","ref":"rankingAdapater","sha":"7f6324c3dae5fcbef556a838ee5317221f89092f","user":{"login":"dciborow","id":9027725,"node_id":"MDQ6VXNlcjkwMjc3MjU=","avatar_url":"https://avatars0.githubusercontent.com/u/9027725?v=4","gravatar_id":"","url":"https://api.github.com/users/dciborow","html_url":"https://github.com/dciborow","followers_url":"https://api.github.com/users/dciborow/followers","following_url":"https://api.github.com/users/dciborow/following{/other_user}","gists_url":"https://api.github.com/users/dciborow/gists{/gist_id}","starred_url":"https://api.github.com/users/dciborow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dciborow/subscriptions","organizations_url":"https://api.github.com/users/dciborow/orgs","repos_url":"https://api.github.com/users/dciborow/repos","events_url":"https://api.github.com/users/dciborow/events{/privacy}","received_events_url":"https://api.github.com/users/dciborow/received_events","type":"User","site_admin":false},"repo":{"id":150156487,"node_id":"MDEwOlJlcG9zaXRvcnkxNTAxNTY0ODc=","name":"mmlspark","full_name":"dciborow/mmlspark","private":false,"owner":{"login":"dciborow","id":9027725,"node_id":"MDQ6VXNlcjkwMjc3MjU=","avatar_url":"https://avatars0.githubusercontent.com/u/9027725?v=4","gravatar_id":"","url":"https://api.github.com/users/dciborow","html_url":"https://github.com/dciborow","followers_url":"https://api.github.com/users/dciborow/followers","following_url":"https://api.github.com/users/dciborow/following{/other_user}","gists_url":"https://api.github.com/users/dciborow/gists{/gist_id}","starred_url":"https://api.github.com/users/dciborow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dciborow/subscriptions","organizations_url":"https://api.github.com/users/dciborow/orgs","repos_url":"https://api.github.com/users/dciborow/repos","events_url":"https://api.github.com/users/dciborow/events{/privacy}","received_events_url":"https://api.github.com/users/dciborow/received_events","type":"User","site_admin":false},"html_url":"https://github.com/dciborow/mmlspark","description":"Microsoft Machine Learning for Apache Spark","fork":true,"url":"https://api.github.com/repos/dciborow/mmlspark","forks_url":"https://api.github.com/repos/dciborow/mmlspark/forks","keys_url":"https://api.github.com/repos/dciborow/mmlspark/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dciborow/mmlspark/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dciborow/mmlspark/teams","hooks_url":"https://api.github.com/repos/dciborow/mmlspark/hooks","issue_events_url":"https://api.github.com/repos/dciborow/mmlspark/issues/events{/number}","events_url":"https://api.github.com/repos/dciborow/mmlspark/events","assignees_url":"https://api.github.com/repos/dciborow/mmlspark/assignees{/user}","branches_url":"https://api.github.com/repos/dciborow/mmlspark/branches{/branch}","tags_url":"https://api.github.com/repos/dciborow/mmlspark/tags","blobs_url":"https://api.github.com/repos/dciborow/mmlspark/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dciborow/mmlspark/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dciborow/mmlspark/git/refs{/sha}","trees_url":"https://api.github.com/repos/dciborow/mmlspark/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dciborow/mmlspark/statuses/{sha}","languages_url":"https://api.github.com/repos/dciborow/mmlspark/languages","stargazers_url":"https://api.github.com/repos/dciborow/mmlspark/stargazers","contributors_url":"https://api.github.com/repos/dciborow/mmlspark/contributors","subscribers_url":"https://api.github.com/repos/dciborow/mmlspark/subscribers","subscription_url":"https://api.github.com/repos/dciborow/mmlspark/subscription","commits_url":"https://api.github.com/repos/dciborow/mmlspark/commits{/sha}","git_commits_url":"https://api.github.com/repos/dciborow/mmlspark/git/commits{/sha}","comments_url":"https://api.github.com/repos/dciborow/mmlspark/comments{/number}","issue_comment_url":"https://api.github.com/repos/dciborow/mmlspark/issues/comments{/number}","contents_url":"https://api.github.com/repos/dciborow/mmlspark/contents/{+path}","compare_url":"https://api.github.com/repos/dciborow/mmlspark/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dciborow/mmlspark/merges","archive_url":"https://api.github.com/repos/dciborow/mmlspark/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dciborow/mmlspark/downloads","issues_url":"https://api.github.com/repos/dciborow/mmlspark/issues{/number}","pulls_url":"https://api.github.com/repos/dciborow/mmlspark/pulls{/number}","milestones_url":"https://api.github.com/repos/dciborow/mmlspark/milestones{/number}","notifications_url":"https://api.github.com/repos/dciborow/mmlspark/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dciborow/mmlspark/labels{/name}","releases_url":"https://api.github.com/repos/dciborow/mmlspark/releases{/id}","deployments_url":"https://api.github.com/repos/dciborow/mmlspark/deployments","created_at":"2018-09-24T19:23:27Z","updated_at":"2018-10-16T15:15:26Z","pushed_at":"2018-10-30T15:04:07Z","git_url":"git://github.com/dciborow/mmlspark.git","ssh_url":"git@github.com:dciborow/mmlspark.git","clone_url":"https://github.com/dciborow/mmlspark.git","svn_url":"https://github.com/dciborow/mmlspark","homepage":"http://aka.ms/spark","size":1774,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"Azure:master","ref":"master","sha":"9060406720d989557e98ce4c6d7e0c6615e79292","user":{"login":"Azure","id":6844498,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY4NDQ0OTg=","avatar_url":"https://avatars0.githubusercontent.com/u/6844498?v=4","gravatar_id":"","url":"https://api.github.com/users/Azure","html_url":"https://github.com/Azure","followers_url":"https://api.github.com/users/Azure/followers","following_url":"https://api.github.com/users/Azure/following{/other_user}","gists_url":"https://api.github.com/users/Azure/gists{/gist_id}","starred_url":"https://api.github.com/users/Azure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Azure/subscriptions","organizations_url":"https://api.github.com/users/Azure/orgs","repos_url":"https://api.github.com/users/Azure/repos","events_url":"https://api.github.com/users/Azure/events{/privacy}","received_events_url":"https://api.github.com/users/Azure/received_events","type":"Organization","site_admin":false},"repo":{"id":93381875,"node_id":"MDEwOlJlcG9zaXRvcnk5MzM4MTg3NQ==","name":"mmlspark","full_name":"Azure/mmlspark","private":false,"owner":{"login":"Azure","id":6844498,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY4NDQ0OTg=","avatar_url":"https://avatars0.githubusercontent.com/u/6844498?v=4","gravatar_id":"","url":"https://api.github.com/users/Azure","html_url":"https://github.com/Azure","followers_url":"https://api.github.com/users/Azure/followers","following_url":"https://api.github.com/users/Azure/following{/other_user}","gists_url":"https://api.github.com/users/Azure/gists{/gist_id}","starred_url":"https://api.github.com/users/Azure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Azure/subscriptions","organizations_url":"https://api.github.com/users/Azure/orgs","repos_url":"https://api.github.com/users/Azure/repos","events_url":"https://api.github.com/users/Azure/events{/privacy}","received_events_url":"https://api.github.com/users/Azure/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Azure/mmlspark","description":"Microsoft Machine Learning for Apache Spark","fork":false,"url":"https://api.github.com/repos/Azure/mmlspark","forks_url":"https://api.github.com/repos/Azure/mmlspark/forks","keys_url":"https://api.github.com/repos/Azure/mmlspark/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Azure/mmlspark/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Azure/mmlspark/teams","hooks_url":"https://api.github.com/repos/Azure/mmlspark/hooks","issue_events_url":"https://api.github.com/repos/Azure/mmlspark/issues/events{/number}","events_url":"https://api.github.com/repos/Azure/mmlspark/events","assignees_url":"https://api.github.com/repos/Azure/mmlspark/assignees{/user}","branches_url":"https://api.github.com/repos/Azure/mmlspark/branches{/branch}","tags_url":"https://api.github.com/repos/Azure/mmlspark/tags","blobs_url":"https://api.github.com/repos/Azure/mmlspark/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Azure/mmlspark/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Azure/mmlspark/git/refs{/sha}","trees_url":"https://api.github.com/repos/Azure/mmlspark/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Azure/mmlspark/statuses/{sha}","languages_url":"https://api.github.com/repos/Azure/mmlspark/languages","stargazers_url":"https://api.github.com/repos/Azure/mmlspark/stargazers","contributors_url":"https://api.github.com/repos/Azure/mmlspark/contributors","subscribers_url":"https://api.github.com/repos/Azure/mmlspark/subscribers","subscription_url":"https://api.github.com/repos/Azure/mmlspark/subscription","commits_url":"https://api.github.com/repos/Azure/mmlspark/commits{/sha}","git_commits_url":"https://api.github.com/repos/Azure/mmlspark/git/commits{/sha}","comments_url":"https://api.github.com/repos/Azure/mmlspark/comments{/number}","issue_comment_url":"https://api.github.com/repos/Azure/mmlspark/issues/comments{/number}","contents_url":"https://api.github.com/repos/Azure/mmlspark/contents/{+path}","compare_url":"https://api.github.com/repos/Azure/mmlspark/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Azure/mmlspark/merges","archive_url":"https://api.github.com/repos/Azure/mmlspark/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Azure/mmlspark/downloads","issues_url":"https://api.github.com/repos/Azure/mmlspark/issues{/number}","pulls_url":"https://api.github.com/repos/Azure/mmlspark/pulls{/number}","milestones_url":"https://api.github.com/repos/Azure/mmlspark/milestones{/number}","notifications_url":"https://api.github.com/repos/Azure/mmlspark/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Azure/mmlspark/labels{/name}","releases_url":"https://api.github.com/repos/Azure/mmlspark/releases{/id}","deployments_url":"https://api.github.com/repos/Azure/mmlspark/deployments","created_at":"2017-06-05T08:23:44Z","updated_at":"2018-10-30T11:46:00Z","pushed_at":"2018-10-30T15:04:09Z","git_url":"git://github.com/Azure/mmlspark.git","ssh_url":"git@github.com:Azure/mmlspark.git","clone_url":"https://github.com/Azure/mmlspark.git","svn_url":"https://github.com/Azure/mmlspark","homepage":"http://aka.ms/spark","size":1483,"stargazers_count":1110,"watchers_count":1110,"language":"Scala","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":240,"mirror_url":null,"archived":false,"open_issues_count":57,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":240,"open_issues":57,"watchers":1110,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Azure/mmlspark/pulls/415"},"html":{"href":"https://github.com/Azure/mmlspark/pull/415"},"issue":{"href":"https://api.github.com/repos/Azure/mmlspark/issues/415"},"comments":{"href":"https://api.github.com/repos/Azure/mmlspark/issues/415/comments"},"review_comments":{"href":"https://api.github.com/repos/Azure/mmlspark/pulls/415/comments"},"review_comment":{"href":"https://api.github.com/repos/Azure/mmlspark/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Azure/mmlspark/pulls/415/commits"},"statuses":{"href":"https://api.github.com/repos/Azure/mmlspark/statuses/7f6324c3dae5fcbef556a838ee5317221f89092f"}},"author_association":"NONE"}}
{"actor":{"display_login":"imatiach-msft"}}