J'ai 2 DataFrame
s comme suit:
J'ai besoin d'union comme celle-ci:
La fonction unionAll
ne fonctionne pas car le nombre et le nom des colonnes sont différents.
Comment puis-je faire ceci?
Dans Scala, il vous suffit d'ajouter toutes les colonnes manquantes sous la forme 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|
+---+--------+---------+-------+
Les deux DataFrames
temporels auront le même ordre de colonnes, car nous effectuons un mappage à travers 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|
+---+--------+---------+-------+
Un moyen très simple de faire cela - select
les colonnes dans le même ordre à partir des images 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'))
Voici une solution pyspark.
Il suppose que si un champ dans df1
est absent de df2
, alors vous ajoutez ce champ manquant à df2
avec des valeurs nulles. Cependant, il suppose également que si le champ existe dans les deux images, mais que le type ou la possibilité de nullité du champ est différent, les deux images sont en conflit et ne peuvent pas être combinées. Dans ce cas, je lè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)
Version modifiée d'Alberto Bonsanto pour conserver l'ordre des colonnes d'origine (OP supposait que l'ordre devait correspondre aux tables d'origine). De plus, la partie match
a généré un avertissement 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): _*))
}
Voici le code pour Python 3.0 utilisant pyspark:
from pyspark.sql import SQLContext
import pyspark
from pyspark.sql.functions import lit
def __orderDFAndAddMissingCols(df, columnsOrderList, dfMissingFields):
''' return ordered dataFrame by the columns order list with null in missing columns '''
if not dfMissingFields: #no missing fields for the df
return df.select(columnsOrderList)
else:
columns = []
for colName in columnsOrderList:
if colName not in dfMissingFields:
columns.append(colName)
else:
columns.append(lit(None).alias(colName))
return df.select(columns)
def __addMissingColumns(df, missingColumnNames):
''' Add missing columns as null in the end of the columns list '''
listMissingColumns = []
for col in missingColumnNames:
listMissingColumns.append(lit(None).alias(col))
return df.select(df.schema.names + listMissingColumns)
def __orderAndUnionDFs( leftDF, rightDF, leftListMissCols, rightListMissCols):
''' return union of data frames with ordered columns by leftDF. '''
leftDfAllCols = __addMissingColumns(leftDF, leftListMissCols)
rightDfAllCols = __orderDFAndAddMissingCols(rightDF, leftDfAllCols.schema.names, rightListMissCols)
return leftDfAllCols.union(rightDfAllCols)
def unionDFs(leftDF,rightDF):
''' 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 leftDF == None:
raise ValueError('leftDF parameter should not be None')
if rightDF == None:
raise ValueError('rightDF parameter should not be None')
#For data frames with equal columns and order- regular union
if leftDF.schema.names == rightDF.schema.names:
return leftDF.union(rightDF)
else: # Different columns
#Save dataFrame columns name list as set
leftDFColList = set(leftDF.schema.names)
rightDFColList = set(rightDF.schema.names)
# Diff columns between leftDF and rightDF
rightListMissCols = list(leftDFColList - rightDFColList)
leftListMissCols = list(rightDFColList - leftDFColList)
return __orderAndUnionDFs(leftDF, rightDF, leftListMissCols, rightListMissCols)
if __== '__main__':
sc = pyspark.SparkContext()
sqlContext = SQLContext(sc)
leftDF = sqlContext.createDataFrame( [(1, 2, 11), (3, 4, 12)] , ('a','b','d'))
rightDF = sqlContext.createDataFrame( [(5, 6 , 9), (7, 8, 10)] , ('b','a','c'))
unionDF = unionDFs(leftDF,rightDF)
print(unionDF.select(unionDF.schema.names).show())
J'ai eu le même problème et en utilisant rejoindre au lieu d'union résolu mon problème. Ainsi, par exemple avec python, au lieu de cette ligne de code: result = left.union(right)
, qui ne pourra pas s'exécuter pour un nombre différent de colonnes, vous devriez utiliser celle-ci:
result = left.join(right, left.columns if (len(left.columns) < len(right.columns)) else right.columns, "outer")
Notez que le deuxième argument contient les colonnes communes entre les deux DataFrames. Si vous ne l'utilisez pas, le résultat aura des colonnes dupliquées, l'une d'elles étant null et l'autre pas. J'espère que ça aide.
Il y a beaucoup de manière concise de traiter cette question avec un sacrifice modéré de performance.
def unionWithDifferentSchema(a: DataFrame, b: DataFrame): DataFrame = {
sparkSession.read.json(a.toJSON.union(b.toJSON).rdd)
}
C'est la fonction qui fait l'affaire. L'utilisation de toJSON avec chaque structure de données crée une union json. Cela préserve le classement et le type de données.
La seule prise est toJSON est relativement cher (cependant, vous obtenez probablement un ralentissement de 10-15%). Cependant, cela garde le code propre.
Voici ma Python:
from pyspark.sql import SparkSession, HiveContext
from pyspark.sql.functions import lit
from pyspark.sql import Row
def customUnion(df1, df2):
cols1 = df1.columns
cols2 = df2.columns
total_cols = sorted(cols1 + list(set(cols2) - set(cols1)))
def expr(mycols, allcols):
def processCols(colname):
if colname in mycols:
return colname
else:
return lit(None).alias(colname)
cols = map(processCols, allcols)
return list(cols)
appended = df1.select(expr(cols1, total_cols)).union(df2.select(expr(cols2, total_cols)))
return appended
Voici un exemple d'utilisation:
data = [
Row(Zip_code=58542, dma='MIN'),
Row(Zip_code=58701, dma='MIN'),
Row(Zip_code=57632, dma='MIN'),
Row(Zip_code=58734, dma='MIN')
]
firstDF = spark.createDataFrame(data)
data = [
Row(Zip_code='534', name='MIN'),
Row(Zip_code='353', name='MIN'),
Row(Zip_code='134', name='MIN'),
Row(Zip_code='245', name='MIN')
]
secondDF = spark.createDataFrame(data)
customUnion(firstDF,secondDF).show()
en voici un autre:
def unite(df1: DataFrame, df2: DataFrame): DataFrame = {
val cols1 = df1.columns.toSet
val cols2 = df2.columns.toSet
val total = (cols1 ++ cols2).toSeq.sorted
val expr1 = total.map(c => {
if (cols1.contains(c)) c else "NULL as " + c
})
val expr2 = total.map(c => {
if (cols2.contains(c)) c else "NULL as " + c
})
df1.selectExpr(expr1:_*).union(
df2.selectExpr(expr2:_*)
)
}
Union et union externe pour la concaténation Pyspark DataFrame. Cela fonctionne pour plusieurs trames de données avec différentes colonnes.
def union_all(*dfs):
return reduce(ps.sql.DataFrame.unionAll, dfs)
def outer_union_all(*dfs):
all_cols = set([])
for df in dfs:
all_cols |= set(df.columns)
all_cols = list(all_cols)
print(all_cols)
def expr(cols, all_cols):
def append_cols(col):
if col in cols:
return col
else:
return sqlfunc.lit(None).alias(col)
cols_ = map(append_cols, all_cols)
return list(cols_)
union_df = union_all(*[df.select(expr(df.columns, all_cols)) for df in dfs])
return union_df