私は、データフレームが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を書くことができると思いますが、私の知る限りでは、彼らはパフォーマンスが低下することがあります。パフォーマンスは、私たちの揮発性および大規模データセット(数テラバイト)のために大きな懸念です。私は単に逃したこの私を行う方法で構築があれば疑問に思うようなタスクは、珍しいことには見えません。
あなたが指定されたインデックスでのアレイからの抽出物要素という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
順序ではなく、任意の整数。
この場合、変換Array
にMap
最初からすると、最も自然に見えます。
- 変換
Array[Struct[Int, String]]
するにはMap[Int, String]
:
result.withColumn("hit_customDimensions_Map",
map_from_arrays(col("hit.customDimensions")("index"), col("hit.customDimensions")("value")))
- そして、あなたは移調方法変更
customDimensions
の列を:
result.withColumn("hit_customDimension" + "_" + i.toString(), col("hit_customDimensions_Map")(i));