web-dev-qa-db-ja.com

Apache Spark and JavaでCSVをDataFrame / DataSetとして解析

私はスパークするのが初めてで、group-by&reduceを使用してCSVから次のものを見つけたいと思っています(1行は雇用されています):

  Department, Designation, costToCompany, State
  Sales, Trainee, 12000, UP
  Sales, Lead, 32000, AP
  Sales, Lead, 32000, LA
  Sales, Lead, 32000, TN
  Sales, Lead, 32000, AP
  Sales, Lead, 32000, TN 
  Sales, Lead, 32000, LA
  Sales, Lead, 32000, LA
  Marketing, Associate, 18000, TN
  Marketing, Associate, 18000, TN
  HR, Manager, 58000, TN

Department、Designation、Statesum(costToCompany)およびTotalEmployeeCountの追加列を使用して、グループのCSVを単純化したい

次のような結果が得られます。

  Dept, Desg, state, empCount, totalCost
  Sales,Lead,AP,2,64000
  Sales,Lead,LA,3,96000  
  Sales,Lead,TN,2,64000

変換とアクションを使用してこれを達成する方法はありますか。それとも、RDDの運用に行くべきでしょうか?

19
mithra

手順

  • クラス(スキーマ)を作成して構造をカプセル化します(アプローチBには必要ありませんが、Javaを使用している場合はコードが読みやすくなります)

    public class Record implements Serializable {
      String department;
      String designation;
      long costToCompany;
      String state;
      // constructor , getters and setters  
    }
    
  • CVS(JSON)ファイルの読み込み

    JavaSparkContext sc;
    JavaRDD<String> data = sc.textFile("path/input.csv");
    //JavaSQLContext sqlContext = new JavaSQLContext(sc); // For previous versions 
    SQLContext sqlContext = new SQLContext(sc); // In Spark 1.3 the Java API and Scala API have been unified
    
    
    JavaRDD<Record> rdd_records = sc.textFile(data).map(
      new Function<String, Record>() {
          public Record call(String line) throws Exception {
             // Here you can use JSON
             // Gson gson = new Gson();
             // gson.fromJson(line, Record.class);
             String[] fields = line.split(",");
             Record sd = new Record(fields[0], fields[1], fields[2].trim(), fields[3]);
             return sd;
          }
    });
    

この時点で、2つのアプローチがあります。

A. SparkSQL

  • テーブルを登録します(定義されたスキーマクラスを使用)

    JavaSchemaRDD table = sqlContext.applySchema(rdd_records, Record.class);
    table.registerAsTable("record_table");
    table.printSchema();
    
  • 希望するQuery-group-byでテーブルを照会します

    JavaSchemaRDD res = sqlContext.sql("
      select department,designation,state,sum(costToCompany),count(*) 
      from record_table 
      group by department,designation,state
    ");
    
  • ここでは、SQLアプローチを使用して、必要な他のクエリを実行することもできます。

B.スパーク

  • 複合キーを使用したマッピング:DepartmentDesignationState

    JavaPairRDD<String, Tuple2<Long, Integer>> records_JPRDD = 
    rdd_records.mapToPair(new
      PairFunction<Record, String, Tuple2<Long, Integer>>(){
        public Tuple2<String, Tuple2<Long, Integer>> call(Record record){
          Tuple2<String, Tuple2<Long, Integer>> t2 = 
          new Tuple2<String, Tuple2<Long,Integer>>(
            record.Department + record.Designation + record.State,
            new Tuple2<Long, Integer>(record.costToCompany,1)
          );
          return t2;
    }
    

    });

  • reduceByKeyは、複合キーを使用して、costToCompany列を合計し、キーごとにレコード数を累積します

    JavaPairRDD<String, Tuple2<Long, Integer>> final_rdd_records = 
     records_JPRDD.reduceByKey(new Function2<Tuple2<Long, Integer>, Tuple2<Long,
     Integer>, Tuple2<Long, Integer>>() {
        public Tuple2<Long, Integer> call(Tuple2<Long, Integer> v1,
        Tuple2<Long, Integer> v2) throws Exception {
            return new Tuple2<Long, Integer>(v1._1 + v2._1, v1._2+ v2._2);
        }
    });
    
39
emecas

CSVファイルは、Spark組み込みCSVリーダー)で解析できます。読み取り成功時にDataFrame/DataSetを返しますDataFrame/DataSetの上に、SQLのような操作を簡単に適用します。

JavaでSpark 2.x(以上))を使用する

別名sparkというSparkSessionオブジェクトを作成します

import org.Apache.spark.sql.SparkSession;

SparkSession spark = SparkSession
    .builder()
    .appName("Java Spark SQL Example")
    .getOrCreate();

StructTypeを使用して行のスキーマを作成

import org.Apache.spark.sql.types.StructType;

StructType schema = new StructType()
    .add("department", "string")
    .add("designation", "string")
    .add("ctc", "long")
    .add("state", "string");

CSVファイルからデータフレームを作成し、スキーマを適用します

Dataset<Row> df = spark.read()
    .option("mode", "DROPMALFORMED")
    .schema(schema)
    .csv("hdfs://path/input.csv");

CSVファイルからデータを読み取るための追加オプション

2つの方法でデータを集約できるようになりました

1. SQLの方法

spark sqlメタストアにテーブルを登録して、SQL操作を実行します

df.createOrReplaceTempView("employee");

登録済みデータフレームでSQLクエリを実行する

Dataset<Row> sqlResult = spark.sql(
    "SELECT department, designation, state, SUM(ctc), COUNT(department)" 
        + " FROM employee GROUP BY department, designation, state");

sqlResult.show(); //for testing

Spark SQL でテーブルを作成せずに、CSVファイルでSQLを直接実行することもできます。


2.オブジェクトチェーンまたはプログラミングまたはJavaのような方法

SQL関数に必要なインポートを行う

import static org.Apache.spark.sql.functions.count;
import static org.Apache.spark.sql.functions.sum;

データフレーム/データセットでgroupByおよびaggを使用して、データでcountおよびsumを実行します

Dataset<Row> dfResult = df.groupBy("department", "designation", "state")
    .agg(sum("ctc"), count("department"));
// After Spark 1.6 columns mentioned in group by will be added to result by default

dfResult.show();//for testing

依存ライブラリ

"org.Apache.spark" % "spark-core_2.11" % "2.0.0" 
"org.Apache.spark" % "spark-sql_2.11" % "2.0.0"
20
mrsrinivas

以下は完全に正しいわけではありませんが、データをジャグリングする方法についてのアイデアを提供するはずです。それはきれいではなく、ケースクラスなどに置き換える必要がありますが、spark api、私はそれで十分だと思います:)を使用する方法の簡単な例として

val rawlines = sc.textfile("hdfs://.../*.csv")
case class Employee(dep: String, des: String, cost: Double, state: String)
val employees = rawlines
  .map(_.split(",") /*or use a proper CSV parser*/
  .map( Employee(row(0), row(1), row(2), row(3) )

# the 1 is the amount of employees (which is obviously 1 per line)
val keyVals = employees.map( em => (em.dep, em.des, em.state), (1 , em.cost))

val results = keyVals.reduceByKey{ a,b =>
    (a._1 + b._1, b._1, b._2) # (a.count + b.count , a.cost + b.cost )
}

#debug output
results.take(100).foreach(println)

results
  .map( keyval => someThingToFormatAsCsvStringOrWhatever )
  .saveAsTextFile("hdfs://.../results")

または、SparkSQLを使用できます。

val sqlContext = new SQLContext(sparkContext)

# case classes can easily be registered as tables
employees.registerAsTable("employees")

val results = sqlContext.sql("""select dep, des, state, sum(cost), count(*) 
  from employees 
  group by dep,des,state"""
4
jkgeyti

JSONの場合、テキストファイルに1行に1つのJSONオブジェクトが含まれている場合、sqlContext.jsonFile(path)を使用してSpark SQLがSchemaRDDとしてロードします(スキーマは次に、テーブルとして登録し、SQLでクエリすることができます。また、レコードごとに1つのJSONオブジェクトを含むRDD[String]としてテキストファイルを手動でロードし、sqlContext.jsonRDD(rdd)を使用してSchemaRDDとして有効にします。jsonRDDは、データを前処理する必要がある場合に便利です。

4
yhuai