Синтаксический анализ CSV как DataFrame / DataSet с Apache Spark и Java

Я новичок в spark, и я хочу использовать group-by & reduce, чтобы найти следующее из CSV (одна строка по занятому):

  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

Я хотел бы упростить о CSV с группой по Отдел, Обозначение, Штат С дополнительными столбцами с sum (costToCompany) и TotalEmployeeCount

должен получить результат, как:

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

есть ли способ достичь этого с помощью преобразований и действий. Или должны ли мы пойти на операции RDD?

4 ответов


процедура

  • создайте класс (схему) для инкапсуляции вашей структуры (это не требуется для подхода 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 подхода:

SparkSQL А.

  • зарегистрировать таблицу (используя определенную схему Класс)

    JavaSchemaRDD table = sqlContext.applySchema(rdd_records, Record.class);
    table.registerAsTable("record_table");
    table.printSchema();
    
  • запрос таблицы с желаемым запросом-group-by

    JavaSchemaRDD res = sqlContext.sql("
      select department,designation,state,sum(costToCompany),count(*) 
      from record_table 
      group by department,designation,state
    ");
    
  • здесь вы также сможете сделать любой другой запрос, который вы хотите, используя подход SQL

Б. Искра

  • отображение с помощью составного ключа:Department,Designation,State

    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);
        }
    });
    

CSV-файл может быть проанализирован с помощью Spark встроенный CSV reader. Он вернется DataFrame / DataSet при успешном чтении файла. На вершине DataFrame / DataSet, вы применяете SQL-подобные операции легко.

Используя Искру 2.x (и выше) С Java

создать объект SparkSession aka spark

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("path/input.csv");

дополнительная опция для чтения данных из CSV-файла

теперь мы можем агрегировать данные двумя способами

1. В SQL способом

зарегистрировать таблицу в spark sql metastore для выполнения операции 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

мы можем даже выполнить SQL непосредственно в CSV-файле без создания таблицы с Spark В SQL


2. Цепочка объектов или программирование или Java-подобный способ

выполните необходимый импорт для функций sql

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

использовать groupBy и agg на dataframe / dataset для выполнения 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"

следующее Может быть не совсем правильным, но это должно дать вам некоторое представление о том, как жонглировать данными. Это не очень красиво, следует заменить классами case и т. д., Но в качестве быстрого примера того, как использовать 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"""

для JSON, если ваш текстовый файл содержит один объект JSON в строке, вы можете использовать sqlContext.jsonFile(path) чтобы Spark SQL загрузил его как SchemaRDD (схема будет выведена автоматически). Затем вы можете зарегистрировать его как таблицу и запросить ее с помощью SQL. Вы также можете вручную загрузить текстовый файл как RDD[String] содержит один объект JSON на запись и использует sqlContext.jsonRDD(rdd) чтобы превратить его в SchemaRDD. jsonRDD полезно, когда вам нужно предварительно обработать ваши данные.