J'ai un Spark Dataframe avec quelques valeurs manquantes. Je voudrais effectuer une imputation simple en remplaçant les valeurs manquantes par la moyenne de cette colonne. Je suis très nouveau sur Spark, donc j'ai été du mal à mettre en œuvre cette logique. Voici ce que j'ai réussi à faire jusqu'à présent:
a) Pour ce faire pour une seule colonne (disons Col A), cette ligne de code semble fonctionner:
df.withColumn("new_Col", when($"ColA".isNull, df.select(mean("ColA"))
.first()(0).asInstanceOf[Double])
.otherwise($"ColA"))
b) Cependant, je n'ai pas pu comprendre comment procéder pour toutes les colonnes de mon dataframe. J'essayais la fonction Carte, mais je crois qu'elle parcourt chaque ligne d'une trame de données
c) Il y a une question similaire sur SO - ici . Et bien que j'aimais la solution (en utilisant des tableaux agrégés et la fusion), j'étais très désireux de savoir s'il y avait est un moyen de le faire en parcourant chaque colonne (je viens de R, donc parcourir chaque colonne en utilisant une fonction d'ordre supérieur comme lapply me semble plus naturel).
Merci!
Spark> = 2.2
Vous pouvez utiliser org.Apache.spark.ml.feature.Imputer
(qui prend en charge les stratégies moyenne et médiane).
Scala:
import org.Apache.spark.ml.feature.Imputer
val imputer = new Imputer()
.setInputCols(df.columns)
.setOutputCols(df.columns.map(c => s"${c}_imputed"))
.setStrategy("mean")
imputer.fit(df).transform(df)
Python:
from pyspark.ml.feature import Imputer
imputer = Imputer(
inputCols=df.columns,
outputCols=["{}_imputed".format(c) for c in df.columns]
)
imputer.fit(df).transform(df)
Spark <2,2
Vous voilà:
import org.Apache.spark.sql.functions.mean
df.na.fill(df.columns.Zip(
df.select(df.columns.map(mean(_)): _*).first.toSeq
).toMap)
où
df.columns.map(mean(_)): Array[Column]
calcule une moyenne pour chaque colonne,
df.select(_: *).first.toSeq: Seq[Any]
collecte des valeurs agrégées et convertit la ligne en Seq[Any]
(Je sais que ce n'est pas optimal mais c'est l'API avec laquelle nous devons travailler),
df.columns.Zip(_).toMap: Map[String,Any]
crée aMap: Map[String, Any]
qui correspond du nom de la colonne à sa moyenne, et enfin:
df.na.fill(_): DataFrame
remplit les valeurs manquantes en utilisant:
fill: Map[String, Any] => DataFrame
de DataFrameNaFunctions
.
Pour ingore NaN
entrées, vous pouvez remplacer:
df.select(df.columns.map(mean(_)): _*).first.toSeq
avec:
import org.Apache.spark.sql.functions.{col, isnan, when}
df.select(df.columns.map(
c => mean(when(!isnan(col(c)), col(c)))
): _*).first.toSeq
Pour imputer la médiane (au lieu de la moyenne) dans PySpark <2,2
## filter numeric cols
num_cols = [col_type[0] for col_type in filter(lambda dtype: dtype[1] in {"bigint", "double", "int"}, df.dtypes)]
### Compute a dict with <col_name, median_value>
median_dict = dict()
for c in num_cols:
median_dict[c] = df.stat.approxQuantile(c, [0.5], 0.001)[0]
Ensuite, appliquez na.fill
df_imputed = df.na.fill(median_dict)
Pour PySpark, voici le code que j'ai utilisé:
mean_dict = { col: 'mean' for col in df.columns }
col_avgs = df.agg( mean_dict ).collect()[0].asDict()
col_avgs = { k[4:-1]: v for k,v in col_avgs.iteritems() }
df.fillna( col_avgs ).show()
Les quatre étapes sont les suivantes:
mean_dict
Mappant les noms des colonnes à l'opération d'agrégation (moyenne)col_avgs
col_avgs
Commencent par avg(
Et se terminent par )
, Par ex. avg(col1)
. Retirez les parenthèses.col_avgs