スパークデータフレーム内の配列の列にマップすることによって、新しい列を作成します。

マティアスHauert:

私は、データフレームがAVROファイル(これらは、Googleアナリティクスからのトラッキングデータが含まれている)からインポートスパーク2.4を変換したいです。

このようなスキーマルックスの興味深い部分:

root
 |-- visitorId: long (nullable = true)
 |-- visitNumber: long (nullable = true)
 |-- visitId: long (nullable = true)
 |-- visitStartTime: long (nullable = true)
 |-- date: string (nullable = true)
 |-- hits: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- hitNumber: long (nullable = true)
 |    |    |-- time: long (nullable = true)
 |    |    |-- hour: long (nullable = true)
 |    |    |-- minute: long (nullable = true)
 |    |    |-- customDimensions: array (nullable = true)
 |    |    |    |-- element: struct (containsNull = true)
 |    |    |    |    |-- index: long (nullable = true)
 |    |    |    |    |-- value: string (nullable = true)

得られたデータセットは、深くネストされた構造体なしでほぼ平坦であるべきです。ようなアレイは、hits容易にすることによって達成される独自の行、持つべきexplode機能。以下のような配列はhits.customDimensionsトリッキーです。各配列要素は有するindex(ないフィールドではない配列位置に対応する)を、各可能な値のために、新しい列を作成しなければなりません。最終的なスキーマは次のようになります。

root
 |-- visitorId: long (nullable = true)
 |-- visitNumber: long (nullable = true)
 |-- visitId: long (nullable = true)
 |-- visitStartTime: long (nullable = true)
 |-- hit_date: string (nullable = true)
 |-- hit_hitNumber: long (nullable = true)
 |-- hit_time: long (nullable = true)
 |-- hit_hour: long (nullable = true)
 |-- hit_minute: long (nullable = true)
 |-- hit_customDimension_1: string (nullable = true)
 |-- hit_customDimension_9: string (nullable = true)

データで見つかった実際の指標に応じて、hit_customDimension_Xより頻繁に発生する可能性があります。

データセットは、これまでのところ、このように変換されます。

import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.SparkSession;

import static org.apache.spark.sql.functions.col;
import static org.apache.spark.sql.functions.explode;
import static org.apache.spark.sql.functions.explode_outer;

public class Flattener {
  public static void main(String[] args) {
    String avroFiles = String.join(",", args); // @TODO: named parameters
    SparkConf conf = new SparkConf().setAppName("Simple Application").set("spark.ui.port", "8080");
    SparkSession spark = SparkSession.builder().appName("Simple Application").config(conf).getOrCreate();
    SQLContext sqlContext = spark.sqlContext();
    Dataset<Row> sessions = spark.read().format("avro").load(avroFiles).limit(1000);

    //explode the hits to more rows, remove original array
    sessions = sessions.withColumn("hit", explode_outer(col("hits"))).drop(col("hits"));

    //sample the distinct indinces
    Dataset<Row> r = result.sample(0.1).select(explode(col("hit.customDimensions"))).select(col("col.index")).distinct();
    List<Long> indices = new LinkedList<Long>();
    r.foreach(dr -> {
        indices.add(dr.getLong(0));
    });
    Iterator<Long> l = indices.iterator();
    // for each found index, extract the array element to its own column
    while (l.hasNext()) {
        Long i = l.next();
        result.withColumn("hit_customDimension" + "_" + i.toString(), array_find("hit.customDimensions", "index", i));
    }

    //TODO: move hit column up one level
}

問題は次のとおりです。そのようなありませんarray_find機能が。私が見つかりました。フィルタ機能を(約節参照配列の列のフィルタリング)が、行ではなく、配列の要素をフィルタリングするようです。

私は1つは、これを行うためにUDFを書くことができると思いますが、私の知る限りでは、彼らはパフォーマンスが低下することがあります。パフォーマンスは、私たちの揮発性および大規模データセット(数テラバイト)のために大きな懸念です。私は単に逃したこの私を行う方法で構築があれば疑問に思うようなタスクは、珍しいことには見えません。

Egor 123:

あなたが指定されたインデックスでのアレイからの抽出物要素というSQL関数を探しているようです。

この関数は、Spark APIですでに存在しているが、それはない別の関数としてではなくとして実装されているので、「隠された」のようなものapplyでメソッドColumnクラス。確認してくださいscaladocを

  /**
   * Extracts a value or values from a complex type.
   * The following types of extraction are supported:
   * <ul>
   * <li>Given an Array, an integer ordinal can be used to retrieve a single value.</li>
   * <li>Given a Map, a key of the correct type can be used to retrieve an individual value.</li>
   * <li>Given a Struct, a string fieldName can be used to extract that field.</li>
   * <li>Given an Array of Structs, a string fieldName can be used to extract filed
   *    of every struct in that array, and return an Array of fields.</li>
   * </ul>
   * @group expr_ops
   * @since 1.4.0
   */
  def apply(extraction: Any): Column

だから、私はあなたを置き換えるために提案するarray_find(...)col("hit.customDimensions")(i)

result.withColumn("hit_customDimension" + "_" + i.toString(), col("hit.customDimensions")(i));

[UPD]

正当コメントで指摘したように、customDimensions疎なアレイであってもよいindex順序ではなく、任意の整数。

この場合、変換ArrayMap最初からすると、最も自然に見えます。

  1. 変換Array[Struct[Int, String]]するにはMap[Int, String]
result.withColumn("hit_customDimensions_Map", 
  map_from_arrays(col("hit.customDimensions")("index"), col("hit.customDimensions")("value")))

  1. そして、あなたは移調方法変更customDimensionsの列を:
result.withColumn("hit_customDimension" + "_" + i.toString(), col("hit_customDimensions_Map")(i));

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=19761&siteId=1