web-dev-qa-db-fra.com

Remplacez les chaînes vides par des valeurs None / null dans DataFrame

J'ai un Spark 1.5.0 DataFrame avec un mélange de null et de chaînes vides dans la même colonne. Je veux convertir toutes les chaînes vides de toutes les colonnes en null (None, en Python). Le DataFrame peut avoir des centaines de colonnes, donc j'essaie d'éviter les manipulations codées en dur de chaque colonne.

Voir ma tentative ci-dessous, ce qui entraîne une erreur.

from pyspark.sql import SQLContext
sqlContext = SQLContext(sc)

## Create a test DataFrame
testDF = sqlContext.createDataFrame([Row(col1='foo', col2=1), Row(col1='', col2=2), Row(col1=None, col2='')])
testDF.show()
## +----+----+
## |col1|col2|
## +----+----+
## | foo|   1|
## |    |   2|
## |null|null|
## +----+----+

## Try to replace an empty string with None/null
testDF.replace('', None).show()
## ValueError: value should be a float, int, long, string, list, or Tuple

## A string value of null (obviously) doesn't work...
testDF.replace('', 'null').na.drop(subset='col1').show()
## +----+----+
## |col1|col2|
## +----+----+
## | foo|   1|
## |null|   2|
## +----+----+
21
dnlbrky

C'est aussi simple que cela:

from pyspark.sql.functions import col, when

def blank_as_null(x):
    return when(col(x) != "", col(x)).otherwise(None)

dfWithEmptyReplaced = testDF.withColumn("col1", blank_as_null("col1"))

dfWithEmptyReplaced.show()
## +----+----+
## |col1|col2|
## +----+----+
## | foo|   1|
## |null|   2|
## |null|null|
## +----+----+

dfWithEmptyReplaced.na.drop().show()
## +----+----+
## |col1|col2|
## +----+----+
## | foo|   1|
## +----+----+

Si vous souhaitez remplir plusieurs colonnes, vous pouvez par exemple réduire:

to_convert = set([...]) # Some set of columns

reduce(lambda df, x: df.withColumn(x, blank_as_null(x)), to_convert, testDF)

ou utilisez la compréhension:

exprs = [
    blank_as_null(x).alias(x) if x in to_convert else x for x in testDF.columns]

testDF.select(*exprs)

Si vous souhaitez opérer spécifiquement sur les champs de chaîne, veuillez vérifier la réponse par robin-loxley .

29
zero323

Ma solution est bien meilleure que toutes les solutions que j'ai vues jusqu'à présent, qui peuvent traiter autant de champs que vous le souhaitez, voyez la petite fonction comme suit:

  // Replace empty Strings with null values
  private def setEmptyToNull(df: DataFrame): DataFrame = {
    val exprs = df.schema.map { f =>
      f.dataType match {
        case StringType => when(length(col(f.name)) === 0, lit(null: String).cast(StringType)).otherwise(col(f.name)).as(f.name)
        case _ => col(f.name)
      }
    }

    df.select(exprs: _*)
  }

Vous pouvez facilement réécrire la fonction ci-dessus en Python.

J'ai appris cette astuce de @ liancheng

10
soulmachine

Ajoutez simplement en plus des réponses de zero323 et de soulmachine. Pour convertir pour tous les champs StringType.

from pyspark.sql.types import StringType
string_fields = []
for i, f in enumerate(test_df.schema.fields):
    if isinstance(f.dataType, StringType):
        string_fields.append(f.name)
7
Robin Loxley

Les FDU ne sont pas terriblement efficaces. La façon correcte de le faire à l'aide d'une méthode intégrée est la suivante:

df = df.withColumn('myCol', when(col('myCol') == '', None).otherwise(col('myCol')))
5
bloodrootfc