如何检测Spark DataFrame是否具有列

时间:2016-03-09 22:40:16

标签: scala apache-spark dataframe apache-spark-sql

当我在Spark SQL中从JSON文件创建DataFrame时,如何在调用.select之前判断给定列是否存在

示例JSON架构:

{
  "a": {
    "b": 1,
    "c": 2
  }
}

这就是我想要做的事情:

potential_columns = Seq("b", "c", "d")
df = sqlContext.read.json(filename)
potential_columns.map(column => if(df.hasColumn(column)) df.select(s"a.$column"))

但我找不到hasColumn的好功能。我得到的最接近的是测试列是否在这个有点笨拙的数组中:

scala> df.select("a.*").columns
res17: Array[String] = Array(b, c)

11 个答案:

答案 0 :(得分:59)

假设它存在并让它以Try失败。简单明了,支持任意嵌套:

import scala.util.Try
import org.apache.spark.sql.DataFrame

def hasColumn(df: DataFrame, path: String) = Try(df(path)).isSuccess

val df = sqlContext.read.json(sc.parallelize(
  """{"foo": [{"bar": {"foobar": 3}}]}""" :: Nil))

hasColumn(df, "foobar")
// Boolean = false

hasColumn(df, "foo")
// Boolean = true

hasColumn(df, "foo.bar")
// Boolean = true

hasColumn(df, "foo.bar.foobar")
// Boolean = true

hasColumn(df, "foo.bar.foobaz")
// Boolean = false

甚至更简单:

val columns = Seq(
  "foobar", "foo", "foo.bar", "foo.bar.foobar", "foo.bar.foobaz")

columns.flatMap(c => Try(df(c)).toOption)
// Seq[org.apache.spark.sql.Column] = List(
//   foo, foo.bar AS bar#12, foo.bar.foobar AS foobar#13)

Python等价物:

from pyspark.sql.utils import AnalysisException
from pyspark.sql import Row


def has_column(df, col):
    try:
        df[col]
        return True
    except AnalysisException:
        return False

df = sc.parallelize([Row(foo=[Row(bar=Row(foobar=3))])]).toDF()

has_column(df, "foobar")
## False

has_column(df, "foo")
## True

has_column(df, "foo.bar")
## True

has_column(df, "foo.bar.foobar")
## True

has_column(df, "foo.bar.foobaz")
## False

答案 1 :(得分:26)

我通常使用的另一个选项是

df.columns.contains("column-name-to-check")

返回布尔值

答案 2 :(得分:12)

实际上你甚至不需要调用select来使用列,你可以在数据帧本身上调用它

// define test data
case class Test(a: Int, b: Int)
val testList = List(Test(1,2), Test(3,4))
val testDF = sqlContext.createDataFrame(testList)

// define the hasColumn function
def hasColumn(df: org.apache.spark.sql.DataFrame, colName: String) = df.columns.contains(colName)

// then you can just use it on the DF with a given column name
hasColumn(testDF, "a")  // <-- true
hasColumn(testDF, "c")  // <-- false

或者你可以使用pimp my library模式定义一个隐式类,这样就可以直接在你的数据框上使用hasColumn方法

implicit class DataFrameImprovements(df: org.apache.spark.sql.DataFrame) {
    def hasColumn(colName: String) = df.columns.contains(colName)
}

然后您可以将其用作:

testDF.hasColumn("a") // <-- true
testDF.hasColumn("c") // <-- false

答案 3 :(得分:2)

您的另一个选择是在intersectdf.columns上进行一些数组操作(在本例中为potential_columns)。

// Loading some data (so you can just copy & paste right into spark-shell)
case class Document( a: String, b: String, c: String)
val df = sc.parallelize(Seq(Document("a", "b", "c")), 2).toDF

// The columns we want to extract
val potential_columns = Seq("b", "c", "d")

// Get the intersect of the potential columns and the actual columns, 
// we turn the array of strings into column objects
// Finally turn the result into a vararg (: _*)
df.select(potential_columns.intersect(df.columns).map(df(_)): _*).show

唉,这对你上面的内部对象场景不起作用。您需要查看该模式。

我要将您的potential_columns更改为完全限定的列名

val potential_columns = Seq("a.b", "a.c", "a.d")

// Our object model
case class Document( a: String, b: String, c: String)
case class Document2( a: Document, b: String, c: String)

// And some data...
val df = sc.parallelize(Seq(Document2(Document("a", "b", "c"), "c2")), 2).toDF

// We go through each of the fields in the schema.
// For StructTypes we return an array of parentName.fieldName
// For everything else we return an array containing just the field name
// We then flatten the complete list of field names
// Then we intersect that with our potential_columns leaving us just a list of column we want
// we turn the array of strings into column objects
// Finally turn the result into a vararg (: _*)
df.select(df.schema.map(a => a.dataType match { case s : org.apache.spark.sql.types.StructType => s.fieldNames.map(x => a.name + "." + x) case _ => Array(a.name) }).flatMap(x => x).intersect(potential_columns).map(df(_)) : _*).show

这只是深入一层,所以为了使它更通用,你将不得不做更多的工作。

答案 4 :(得分:2)

Try不是最优的,因为它会在做出决定之前评估Try内的表达式。

对于大型数据集,请使用Scala中的以下内容:

df.schema.fieldNames.contains("column_name")

答案 5 :(得分:1)

对于嵌套列,您可以使用

df.schema.simpleString().find('column_name')

答案 6 :(得分:0)

对于那些偶然发现这种寻找Python解决方案的人,我使用:

toggleUnSelectLojas: function(value, id)
  {          

     console.log(' Teste toggleUnSelectMarkets value : ', value)
     console.log(' Teste toggleUnSelectMarkets id : ', id)
  },

当我使用Python尝试@Jai Prakash对if 'column_name_to_check' in df.columns: # do something 的回答时,我得到了df.columns.contains('column-name-to-check')

答案 7 :(得分:0)

如果您在加载json时使用架构定义将其切细,则无需检查该列。如果不在json源中,它将显示为空列。

        val schemaJson = """
  {
      "type": "struct",
      "fields": [
          {
            "name": field1
            "type": "string",
            "nullable": true,
            "metadata": {}
          },
          {
            "name": field2
            "type": "string",
            "nullable": true,
            "metadata": {}
          }
      ]
  }
        """
    val schema = DataType.fromJson(schemaJson).asInstanceOf[StructType]

    val djson = sqlContext.read
    .schema(schema )
    .option("badRecordsPath", readExceptionPath)
    .json(dataPath)

答案 8 :(得分:0)

在PySpark中,df.columns为您提供数据框中的列列表,因此 df.columns中的“ colName” 将返回True或False。试试看。祝你好运!

答案 9 :(得分:0)

在pyspark中,您只需运行即可

df.columns中的

'field'

答案 10 :(得分:-1)

def hasColumn(df: org.apache.spark.sql.DataFrame, colName: String) =
  Try(df.select(colName)).isSuccess

使用上述功能检查包含嵌套列名称的列是否存在。