web-dev-qa-db-fra.com

Analyser CSV en tant que DataFrame / DataSet avec Apache Spark et Java

Je suis nouveau sur spark, et je veux utiliser le regroupement et la réduction pour trouver les éléments suivants dans CSV (une ligne par employé):

  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

Je voudrais simplifier le sujet CSV avec le groupe par Département, Désignation, État avec des colonnes supplémentaires avec sum (costToCompany) et TotalEmployeeCount

Devrait obtenir un résultat comme:

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

Existe-t-il un moyen d'y parvenir en utilisant des transformations et des actions. Ou devrions-nous opter pour des opérations RDD?

19
mithra

Procédure

  • Créez une classe (schéma) pour encapsuler votre structure (ce n'est pas nécessaire pour l'approche B, mais cela faciliterait la lecture de votre code si vous utilisez Java)

    public class Record implements Serializable {
      String department;
      String designation;
      long costToCompany;
      String state;
      // constructor , getters and setters  
    }
    
  • Chargement du fichier 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;
          }
    });
    

À ce stade, vous avez 2 approches:

A. SparkSQL

  • Enregistrer une table (en utilisant la classe de schéma définie)

    JavaSchemaRDD table = sqlContext.applySchema(rdd_records, Record.class);
    table.registerAsTable("record_table");
    table.printSchema();
    
  • Recherchez la table avec le groupe de requêtes souhaité

    JavaSchemaRDD res = sqlContext.sql("
      select department,designation,state,sum(costToCompany),count(*) 
      from record_table 
      group by department,designation,state
    ");
    
  • Ici, vous pourrez également effectuer toute autre requête que vous souhaitez, en utilisant une approche SQL

B. Spark

  • Mappage à l'aide d'une clé composite: 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 à l'aide de la clé composite, en additionnant la colonne costToCompany et en accumulant le nombre d'enregistrements par clé

    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

Le fichier CSV peut être analysé avec Spark lecteur CSV intégré . Il renverra DataFrame/DataSet lors de la lecture réussie En plus de DataFrame/DataSet, vous appliquez facilement des opérations de type SQL.

Utilisation de Spark 2.x (et supérieur) avec Java

Créer un objet SparkSession aka spark

import org.Apache.spark.sql.SparkSession;

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

Créer un schéma pour la ligne avec StructType

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

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

Créer une trame de données à partir d'un fichier CSV et lui appliquer un schéma

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

plus d'option sur la lecture des données du fichier CSV

Maintenant, nous pouvons agréger les données de 2 façons

1. Manière SQL

Enregistrer une table dans spark sql metastore pour effectuer une opération SQL

df.createOrReplaceTempView("employee");

Exécuter une requête SQL sur une trame de données enregistrée

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

sqlResult.show(); //for testing

Nous pouvons même exécuter SQL directement sur un fichier CSV sans créer de table avec Spark SQL


2. Chaînage d'objets ou programmation ou façon Java

Effectuez l'importation nécessaire pour les fonctions SQL

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

Utilisez groupBy et agg sur le cadre de données/jeu de données pour effectuer count et sum sur les données

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

bibliothèques dépendantes

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

Ce qui suit n'est peut-être pas tout à fait correct, mais il devrait vous donner une idée de la façon de jongler avec les données. Ce n'est pas joli, devrait être remplacé par des classes de cas, etc., mais comme un exemple rapide de la façon d'utiliser l'api spark, j'espère que cela suffit :)

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")

Ou vous pouvez utiliser 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

Pour JSON, si votre fichier texte contient un objet JSON par ligne, vous pouvez utiliser sqlContext.jsonFile(path) pour laisser Spark SQL le charger en tant que SchemaRDD (le schéma sera automatiquement déduit.) Ensuite, vous pouvez l'enregistrer en tant que table et l'interroger avec SQL. Vous pouvez également charger manuellement le fichier texte en tant que RDD[String] contenant un objet JSON par enregistrement et utiliser sqlContext.jsonRDD(rdd) pour le transformer en SchemaRDD. jsonRDD est utile lorsque vous devez prétraiter vos données.

4
yhuai