84 votes

Comment effectuer une union sur deux DataFrames avec des quantités différentes de colonnes dans spark ?

J'en ai deux. DataFrame s :

Source data

J'ai besoin d'un syndicat comme celui-ci :

enter image description here

El unionAll ne fonctionne pas car le nombre et le nom des colonnes sont différents.

Comment puis-je le faire ?

63voto

Alberto Bonsanto Points 877

En Scala, il suffit d'ajouter toutes les colonnes manquantes en tant que nulls .

import org.apache.spark.sql.functions._

// let df1 and df2 the Dataframes to merge
val df1 = sc.parallelize(List(
  (50, 2),
  (34, 4)
)).toDF("age", "children")

val df2 = sc.parallelize(List(
  (26, true, 60000.00),
  (32, false, 35000.00)
)).toDF("age", "education", "income")

val cols1 = df1.columns.toSet
val cols2 = df2.columns.toSet
val total = cols1 ++ cols2 // union

def expr(myCols: Set[String], allCols: Set[String]) = {
  allCols.toList.map(x => x match {
    case x if myCols.contains(x) => col(x)
    case _ => lit(null).as(x)
  })
}

df1.select(expr(cols1, total):_*).unionAll(df2.select(expr(cols2, total):_*)).show()

+---+--------+---------+-------+
|age|children|education| income|
+---+--------+---------+-------+
| 50|       2|     null|   null|
| 34|       4|     null|   null|
| 26|    null|     true|60000.0|
| 32|    null|    false|35000.0|
+---+--------+---------+-------+

Mise à jour

Les deux temporels DataFrames auront le même ordre de colonnes, parce que nous faisons un mappage par le biais de total dans les deux cas.

df1.select(expr(cols1, total):_*).show()
df2.select(expr(cols2, total):_*).show()

+---+--------+---------+------+
|age|children|education|income|
+---+--------+---------+------+
| 50|       2|     null|  null|
| 34|       4|     null|  null|
+---+--------+---------+------+

+---+--------+---------+-------+
|age|children|education| income|
+---+--------+---------+-------+
| 26|    null|     true|60000.0|
| 32|    null|    false|35000.0|
+---+--------+---------+-------+

0 votes

J'exécute exactement la même commande et les colonnes ne sont pas dans le même ordre, lorsque j'exécute l'union les valeurs sont fausses.

0 votes

Il est intéressant de noter que, dans spark 1.5.2, il semble que l'ordre ait de l'importance (je crois qu'il ne devrait pas). Cependant, je pense que vos colonnes devraient avoir le même ordre que total (voir la carte dans expr ).

3 votes

L'ordre des colonnes est important. Voir issues.apache.org/jira/browse/SPARK-20660

20voto

Eli B Points 31

Voici le code pour Python 3.0 en utilisant pyspark :

from pyspark.sql.functions import lit

def __order_df_and_add_missing_cols(df, columns_order_list, df_missing_fields):
    """ return ordered dataFrame by the columns order list with null in missing columns """
    if not df_missing_fields:  # no missing fields for the df
        return df.select(columns_order_list)
    else:
        columns = []
        for colName in columns_order_list:
            if colName not in df_missing_fields:
                columns.append(colName)
            else:
                columns.append(lit(None).alias(colName))
        return df.select(columns)

def __add_missing_columns(df, missing_column_names):
    """ Add missing columns as null in the end of the columns list """
    list_missing_columns = []
    for col in missing_column_names:
        list_missing_columns.append(lit(None).alias(col))

    return df.select(df.schema.names + list_missing_columns)

def __order_and_union_d_fs(left_df, right_df, left_list_miss_cols, right_list_miss_cols):
    """ return union of data frames with ordered columns by left_df. """
    left_df_all_cols = __add_missing_columns(left_df, left_list_miss_cols)
    right_df_all_cols = __order_df_and_add_missing_cols(right_df, left_df_all_cols.schema.names,
                                                        right_list_miss_cols)
    return left_df_all_cols.union(right_df_all_cols)

def union_d_fs(left_df, right_df):
    """ Union between two dataFrames, if there is a gap of column fields,
     it will append all missing columns as nulls """
    # Check for None input
    if left_df is None:
        raise ValueError('left_df parameter should not be None')
    if right_df is None:
        raise ValueError('right_df parameter should not be None')
        # For data frames with equal columns and order- regular union
    if left_df.schema.names == right_df.schema.names:
        return left_df.union(right_df)
    else:  # Different columns
        # Save dataFrame columns name list as set
        left_df_col_list = set(left_df.schema.names)
        right_df_col_list = set(right_df.schema.names)
        # Diff columns between left_df and right_df
        right_list_miss_cols = list(left_df_col_list - right_df_col_list)
        left_list_miss_cols = list(right_df_col_list - left_df_col_list)
        return __order_and_union_d_fs(left_df, right_df, left_list_miss_cols, right_list_miss_cols)

2 votes

Ah, c'est reparti, on n'a aucune idée de Python, Glue, Spark, on se contente de copier-coller des trucs et de les faire fonctionner.

16voto

Rags Points 680

Un moyen très simple de le faire - select les colonnes dans le même ordre à partir des deux cadres de données et utiliser unionAll

df1.select('code', 'date', 'A', 'B', 'C', lit(None).alias('D'), lit(None).alias('E'))\
   .unionAll(df2.select('code', 'date', lit(None).alias('A'), 'B', 'C', 'D', 'E'))

7 votes

unionAll() a été déprécié depuis la version 2.0.0 en faveur de l'option union()

1 votes

Deuxièmement : pour moi, lit(None) échoue avec RuntimeException : Unsupported literal type class scala.None$ None J'ai donc dû le changer en lit(null)

12voto

conradlee Points 1022

Voici une solution pyspark.

Il part du principe que si un champ dans df1 est absent de df2 puis vous ajoutez le champ manquant à df2 avec des valeurs nulles. Cependant, il suppose également que si le champ existe dans les deux bases de données, mais que le type ou la nullité du champ est différent, alors les deux bases de données sont en conflit et ne peuvent être combinées. Dans ce cas, je soulève un TypeError .

from pyspark.sql.functions import lit

def harmonize_schemas_and_combine(df_left, df_right):
    left_types = {f.name: f.dataType for f in df_left.schema}
    right_types = {f.name: f.dataType for f in df_right.schema}
    left_fields = set((f.name, f.dataType, f.nullable) for f in df_left.schema)
    right_fields = set((f.name, f.dataType, f.nullable) for f in df_right.schema)

    # First go over left-unique fields
    for l_name, l_type, l_nullable in left_fields.difference(right_fields):
        if l_name in right_types:
            r_type = right_types[l_name]
            if l_type != r_type:
                raise TypeError, "Union failed. Type conflict on field %s. left type %s, right type %s" % (l_name, l_type, r_type)
            else:
                raise TypeError, "Union failed. Nullability conflict on field %s. left nullable %s, right nullable %s"  % (l_name, l_nullable, not(l_nullable))
        df_right = df_right.withColumn(l_name, lit(None).cast(l_type))

    # Now go over right-unique fields
    for r_name, r_type, r_nullable in right_fields.difference(left_fields):
        if r_name in left_types:
            l_type = left_types[r_name]
            if r_type != l_type:
                raise TypeError, "Union failed. Type conflict on field %s. right type %s, left type %s" % (r_name, r_type, l_type)
            else:
                raise TypeError, "Union failed. Nullability conflict on field %s. right nullable %s, left nullable %s" % (r_name, r_nullable, not(r_nullable))
        df_left = df_left.withColumn(r_name, lit(None).cast(r_type))    

    # Make sure columns are in the same order
    df_left = df_left.select(df_right.columns)

    return df_left.union(df_right)

1 votes

Bizarrement, lorsque j'exécute ce programme, j'obtiens une pyspark.sql.utils.AnalysisException: u"unresolved operator 'Union;" . Cela semble être une sorte de bogue d'étincelle - peut-être que quelqu'un d'autre sait ce qui se passe ?

0 votes

Essayez de définir votre contexte sqlCtx.sql("SET spark.sql.parquet.binaryAsString=true") cela a résolu mon problème

0 votes

@conradlee juste pour info - union a remplacé unionAll depuis Spark v2.0 - donc peut-être que vous êtes sur Spark < v2.0 ?

8voto

swdev Points 474

Modification de la version d'Alberto Bonsanto pour préserver l'ordre original des colonnes (OP impliquait que l'ordre devait correspondre aux tables originales). De plus, le match a provoqué un avertissement d'Intellij.

Voici ma version :

def unionDifferentTables(df1: DataFrame, df2: DataFrame): DataFrame = {

  val cols1 = df1.columns.toSet
  val cols2 = df2.columns.toSet
  val total = cols1 ++ cols2 // union

  val order = df1.columns ++  df2.columns
  val sorted = total.toList.sortWith((a,b)=> order.indexOf(a) < order.indexOf(b))

  def expr(myCols: Set[String], allCols: List[String]) = {
      allCols.map( {
        case x if myCols.contains(x) => col(x)
        case y => lit(null).as(y)
      })
  }

  df1.select(expr(cols1, sorted): _*).unionAll(df2.select(expr(cols2, sorted): _*))
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X