如何将csv文件转换为rdd

时间:2014-06-19 05:35:28

标签: scala apache-spark

我是新来的火花。我想对CSV记录中的特定数据执行一些操作。

我试图读取CSV文件并将其转换为RDD。我的进一步操作基于CSV文件中提供的标题。

(来自评论) 到目前为止,这是我的代码:

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

我可以像这样得到标题值。我想将其映射到CSV文件中的每条记录。

final String[] header=heading.split(" "); 

我可以像这样得到标题值。我想将其映射到CSV文件中的每条记录。

在java中我使用CSVReader record.getColumnValue(Column header)来获取特定值。我需要做类似的事情。

12 个答案:

答案 0 :(得分:52)

一种简单的方法是保留标题。

我们假设你有一个像.cv这样的文件:

user, topic, hits
om,  scala, 120
daniel, spark, 80
3754978, spark, 1

我们可以定义一个使用第一行解析版本的头类:

class SimpleCSVHeader(header:Array[String]) extends Serializable {
  val index = header.zipWithIndex.toMap
  def apply(array:Array[String], key:String):String = array(index(key))
}

我们可以使用该标题来解决未来的数据:

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

请注意,header只不过是一个简单的数组索引助记符映射。几乎所有这些都可以在数组中元素的序数位置完成,例如user = row(0)

PS:欢迎来到Scala: - )

答案 1 :(得分:16)

您可以使用spark-csv库:https://github.com/databricks/spark-csv

这直接来自文档:

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

答案 2 :(得分:9)

首先,我必须说,如果将标题放在单独的文件中,这要简单得多 - 这是大数据中的惯例。

无论如何,丹尼尔的回答非常好,但它效率低下且有一个错误,所以我要发布自己的错误。低效率是您不需要检查每条记录以查看它是否是标题,您只需要检查每个分区的第一条记录。错误是通过使用.split(","),当条目为空字符串并且出现在记录的开头或结尾时,您可能会抛出异常或获取错误的列 - 以纠正您需要使用{{1} }。所以这是完整的代码:

.split(",", -1)

最后一点,如果你只想钓掉某些专栏,请考虑Parquet。或者,如果您有宽行,至少考虑实施一个延迟评估的拆分函数。

答案 3 :(得分:5)

我们可以使用新的DataFrameRDD来读取和写入CSV数据。 DataFrameRDD优于NormalRDD的优势很少:

  1. DataFrameRDD比NormalRDD快一点,因为我们确定了架构,这有助于在运行时优化很多并为我们提供显着的性能提升。
  2. 即使列以CSV格式移动,它也会自动选择正确的列,因为我们不会将读取数据时出现的列号硬编码为textFile,然后将其拆分然后使用列数获取数据
  3. 在几行代码中,您可以直接阅读CSV文件。
  4. 您将需要拥有此库:在build.sbt中添加

    libraryDependencies += "com.databricks" % "spark-csv_2.10" % "1.2.0"
    

    它的Spark Scala代码:

    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
    

    通过从中获取一些列并

    转换为普通RDD
    val rddData = df.map(x=>Row(x.getAs("colA")))
    //Do other RDD operation on it
    

    将RDD保存为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")
    

    由于标题设置为true,我们将在所有输出文件中获取标题名称。

答案 4 :(得分:4)

我建议直接从驱动程序读取标题,而不是通过Spark。有两个原因:1)这是一条线。分布式方法没有优势。 2)我们在驱动程序中需要这一行,而不是工作节点。

它是这样的:

// 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

现在当您制作RDD时,您可以丢弃标题。

val csvRDD = sc.textFile(filename).filter(_ != header)

然后我们可以从一列创建一个RDD,例如:

val idx = header.split(",").indexOf(columnName)
val columnRDD = csvRDD.map(_.split(",")(idx))

答案 5 :(得分:4)

这是使用Spark / Scala到convert a CSV to RDD的另一个示例。有关更详细的说明,请参阅此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)
}

答案 6 :(得分:2)

另一种方法是使用mapPartitionsWithIndex方法,因为您将获得分区索引号和该分区中所有行的列表。分区0和第0行将是标题

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(",") }

答案 7 :(得分:1)

这个怎么样?

val Delimeter = ","
val textFile = sc.textFile("data.csv").map(line => line.split(Delimeter))

答案 8 :(得分:1)

对于火花scala我通常在我不能使用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))

答案 9 :(得分:0)

我建议你试试

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

您必须在此示例中拥有一个具有文件头规范的类,并将您的数据与架构相关联,并应用mysql中的条件..以获得所需的结果

答案 10 :(得分:0)

我认为您可以尝试将该csv加载到RDD中,然后从该RDD创建数据框,这是从rdd创建数据框的文档:http://spark.apache.org/docs/latest/sql-programming-guide.html#interoperating-with-rdds

答案 11 :(得分:0)

从Spark 2.0开始,CSV可以直接读入DataFrame

如果数据文件没有标题行,那么它将是:

val df = spark.read.csv("file://path/to/data.csv")

这将加载数据,但为每列提供通用名称,如_c0_c1等。

如果有标题,则添加.option("header", "true")将使用第一行来定义DataFrame中的列:

val df = spark.read
  .option("header", "true")
  .csv("file://path/to/data.csv")

举一个具体的例子,让我们说你有一个包含内容的文件:

user,topic,hits
om,scala,120
daniel,spark,80
3754978,spark,1

然后,以下内容将按主题分组:

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