Je suis nouveau pour susciter. Je souhaite effectuer certaines opérations sur des données particulières d'un enregistrement CSV.
J'essaie de lire un fichier CSV et de le convertir en RDD. Mes autres opérations sont basées sur l’en-tête fourni dans le fichier CSV.
(D'après les commentaires) Ceci est mon code jusqu'à présent:
final JavaRDD<String> File = sc.textFile(Filename).cache();
final JavaRDD<String> lines = File.flatMap(new FlatMapFunction<String, String>() {
@Override public Iterable<String> call(String s) {
return Arrays.asList(EOL.split(s));
}
});
final String heading=lines.first().toString();
Je peux obtenir les valeurs d'en-tête comme ceci. Je veux mapper cela à chaque enregistrement dans un fichier CSV.
final String[] header=heading.split(" ");
Je peux obtenir les valeurs d'en-tête comme ceci. Je veux mapper cela à chaque enregistrement dans un fichier CSV.
Dans Java j'utilise CSVReader record.getColumnValue(Column header)
] pour obtenir la valeur particulière. Je dois faire quelque chose de similaire à cela ici.
Une approche simpliste consisterait à avoir un moyen de préserver l’en-tête.
Disons que vous avez un fichier.csv comme:
user, topic, hits
om, scala, 120
daniel, spark, 80
3754978, spark, 1
Nous pouvons définir une classe d'en-tête qui utilise une version analysée de la première ligne:
class SimpleCSVHeader(header:Array[String]) extends Serializable {
val index = header.zipWithIndex.toMap
def apply(array:Array[String], key:String):String = array(index(key))
}
Que nous pouvons utiliser cet en-tête pour traiter les données plus tard:
val csv = sc.textFile("file.csv") // original file
val data = csv.map(line => line.split(",").map(elem => elem.trim)) //lines in rows
val header = new SimpleCSVHeader(data.take(1)(0)) // we build our header with the first line
val rows = data.filter(line => header(line,"user") != "user") // filter the header out
val users = rows.map(row => header(row,"user")
val usersByHits = rows.map(row => header(row,"user") -> header(row,"hits").toInt)
...
Notez que header
n’est guère plus qu’une simple carte d’un mnémonique à l’index du tableau. Presque tout cela pourrait être fait à la place ordinale de l'élément dans le tableau, comme user = row(0)
PS: Bienvenue à Scala :-)
Vous pouvez utiliser la bibliothèque spark-csv: https://github.com/databricks/spark-csv
Ceci provient directement de la documentation:
import org.Apache.spark.sql.SQLContext
SQLContext sqlContext = new SQLContext(sc);
HashMap<String, String> options = new HashMap<String, String>();
options.put("header", "true");
options.put("path", "cars.csv");
DataFrame df = sqlContext.load("com.databricks.spark.csv", options);
Tout d'abord, je dois dire que c'est beaucoup plus simple si vous placez vos en-têtes dans des fichiers séparés - c'est la convention dans le Big Data.
Quoi qu'il en soit, la réponse de Daniel est plutôt bonne, mais elle présente une inefficacité et un bogue, alors je vais poster la mienne. L'inefficacité est qu'il n'est pas nécessaire de vérifier chaque enregistrement pour voir si c'est l'en-tête, il vous suffit de vérifier le premier enregistrement pour chaque partition. Le problème est qu’en utilisant .split(",")
, vous pouvez obtenir une exception ou obtenir la mauvaise colonne lorsque les entrées sont la chaîne vide et se produisent au début ou à la fin de l’enregistrement - pour corriger la nécessité d’utiliser .split(",", -1)
. Alors voici le code complet:
val header =
scala.io.Source.fromInputStream(
hadoop.fs.FileSystem.get(new Java.net.URI(filename), sc.hadoopConfiguration)
.open(new hadoop.fs.Path(path)))
.getLines.head
val columnIndex = header.split(",").indexOf(columnName)
sc.textFile(path).mapPartitions(iterator => {
val head = iterator.next()
if (head == header) iterator else Iterator(head) ++ iterator
})
.map(_.split(",", -1)(columnIndex))
Les points finaux, considérez Parquet si vous voulez pêcher seulement certaines colonnes. Ou au moins, envisagez de mettre en œuvre une fonction de division évaluée paresseusement si vous avez de grandes lignes.
Nous pouvons utiliser le nouveau DataFrameRDD pour lire et écrire les données CSV. DataFrameRDD présente quelques avantages par rapport à NormalRDD:
Vous aurez besoin de cette bibliothèque: Ajoutez-la dans build.sbt
libraryDependencies += "com.databricks" % "spark-csv_2.10" % "1.2.0"
Spark Scala code pour cela:
val sc = new SparkContext(conf)
val sqlContext = new SQLContext(sc)
val csvInPath = "/path/to/csv/abc.csv"
val df = sqlContext.read.format("com.databricks.spark.csv").option("header","true").load(csvInPath)
//format is for specifying the type of file you are reading
//header = true indicates that the first line is header in it
Pour convertir en RDD normal en prenant certaines des colonnes et
val rddData = df.map(x=>Row(x.getAs("colA")))
//Do other RDD operation on it
Enregistrement du format RDD au format CSV:
val aDf = sqlContext.createDataFrame(rddData,StructType(Array(StructField("colANew",StringType,true))))
aDF.write.format("com.databricks.spark.csv").option("header","true").save("/csvOutPath/aCSVOp")
Puisque l'en-tête est défini sur true, nous allons obtenir le nom de l'en-tête dans tous les fichiers de sortie.
Je vous recommande de lire l'en-tête directement à partir du pilote, et non via Spark. Deux raisons à cela: 1) C'est une seule ligne. Une approche distribuée ne présente aucun avantage. 2) Nous avons besoin de cette ligne dans le pilote, pas des nœuds de travail.
Ca fait plutot comme ca:
// Ridiculous amount of code to read one line.
val uri = new Java.net.URI(filename)
val conf = sc.hadoopConfiguration
val fs = hadoop.fs.FileSystem.get(uri, conf)
val path = new hadoop.fs.Path(filename)
val stream = fs.open(path)
val source = scala.io.Source.fromInputStream(stream)
val header = source.getLines.head
Maintenant, lorsque vous créez le RDD, vous pouvez supprimer l’en-tête.
val csvRDD = sc.textFile(filename).filter(_ != header)
Ensuite, nous pouvons créer un RDD à partir d’une colonne, par exemple:
val idx = header.split(",").indexOf(columnName)
val columnRDD = csvRDD.map(_.split(",")(idx))
Voici un autre exemple d'utilisation de Spark/Scala en conversion d'un fichier CSV en fichier RDD . Pour une description plus détaillée, voir ceci post .
def main(args: Array[String]): Unit = {
val csv = sc.textFile("/path/to/your/file.csv")
// split / clean data
val headerAndRows = csv.map(line => line.split(",").map(_.trim))
// get header
val header = headerAndRows.first
// filter out header (eh. just check if the first val matches the first header name)
val data = headerAndRows.filter(_(0) != header(0))
// splits to map (header/value pairs)
val maps = data.map(splits => header.Zip(splits).toMap)
// filter out the user "me"
val result = maps.filter(map => map("user") != "me")
// print result
result.foreach(println)
}
Une autre alternative consiste à utiliser la méthode mapPartitionsWithIndex
pour obtenir le numéro d'index de la partition et une liste de toutes les lignes de cette partition. La partition 0 et la ligne 0 seront l'en-tête
val rows = sc.textFile(path)
.mapPartitionsWithIndex({ (index: Int, rows: Iterator[String]) =>
val results = new ArrayBuffer[(String, Int)]
var first = true
while (rows.hasNext) {
// check for first line
if (index == 0 && first) {
first = false
rows.next // skip the first row
} else {
results += rows.next
}
}
results.toIterator
}, true)
rows.flatMap { row => row.split(",") }
Que dis-tu de ça?
val Delimeter = ","
val textFile = sc.textFile("data.csv").map(line => line.split(Delimeter))
Pour spark scala j'utilise généralement lorsque je ne peux pas utiliser les packages spark csv ...
val sqlContext = new org.Apache.spark.sql.SQLContext(sc)
val rawdata = sc.textFile("hdfs://example.Host:8020/user/example/example.csv")
val header = rawdata.first()
val tbldata = rawdata.filter(_(0) != header(0))
Je pense que vous pouvez essayer de charger ce csv dans un RDD puis de créer un cadre de données à partir de ce RDD. Voici le document de création d'un cadre de données à partir de rdd: http://spark.Apache.org/docs/latest/sql -programming-guide.html # interopérabilité-avec-rdds
Je vous suggère d'essayer
https://spark.Apache.org/docs/latest/sql-programming-guide.html#rdds
JavaRDD<Person> people = sc.textFile("examples/src/main/resources/people.txt").map(
new Function<String, Person>() {
public Person call(String line) throws Exception {
String[] parts = line.split(",");
Person person = new Person();
person.setName(parts[0]);
person.setAge(Integer.parseInt(parts[1].trim()));
return person;
}
});
Vous devez avoir une classe dans cet exemple, personne avec la spécification de votre en-tête de fichier et associer vos données au schéma et appliquer des critères comme dans mysql .. pour obtenir le résultat souhaité
À partir de Spark 2.0, le CSV peut être lu directement dans un DataFrame
.
Si le fichier de données n'a pas de ligne d'en-tête, alors ce serait:
val df = spark.read.csv("file://path/to/data.csv")
Cela va charger les données, mais donnez à chaque colonne des noms génériques comme _c0
, _c1
, Etc.
S'il y a des en-têtes, alors l'ajout de .option("header", "true")
utilisera la première ligne pour définir les colonnes du DataFrame
:
val df = spark.read
.option("header", "true")
.csv("file://path/to/data.csv")
Pour un exemple concret, disons que vous avez un fichier avec le contenu:
user,topic,hits
om,scala,120
daniel,spark,80
3754978,spark,1
Ensuite, les résultats suivants seront regroupés par sujet:
import org.Apache.spark.sql.functions._
import spark.implicits._
val rawData = spark.read
.option("header", "true")
.csv("file://path/to/data.csv")
// specifies the query, but does not execute it
val grouped = rawData.groupBy($"topic").agg(sum($"hits))
// runs the query, pulling the data to the master node
// can fail if the amount of data is too much to fit
// into the master node's memory!
val collected = grouped.collect
// runs the query, writing the result back out
// in this case, changing format to Parquet since that can
// be nicer to work with in Spark
grouped.write.parquet("hdfs://some/output/directory/")
// runs the query, writing the result back out
// in this case, in CSV format with a header and
// coalesced to a single file. This is easier for human
// consumption but usually much slower.
grouped.coalesce(1)
.write
.option("header", "true")
.csv("hdfs://some/output/directory/")