web-dev-qa-db-fra.com

Comment aplatir une structure dans un dataframe Spark?

J'ai un dataframe avec la structure suivante:

 |-- data: struct (nullable = true)
 |    |-- id: long (nullable = true)
 |    |-- keyNote: struct (nullable = true)
 |    |    |-- key: string (nullable = true)
 |    |    |-- note: string (nullable = true)
 |    |-- details: map (nullable = true)
 |    |    |-- key: string
 |    |    |-- value: string (valueContainsNull = true)

Comment il est possible d’aplatir la structure et de créer un nouveau cadre de données:

     |-- id: long (nullable = true)
     |-- keyNote: struct (nullable = true)
     |    |-- key: string (nullable = true)
     |    |-- note: string (nullable = true)
     |-- details: map (nullable = true)
     |    |-- key: string
     |    |-- value: string (valueContainsNull = true)

Y a-t-il quelque chose comme exploser, mais pour les structures?

14
djWann

Cela devrait fonctionner dans Spark 1.6 ou version ultérieure:

df.select(df.col("data.*"))

ou

df.select(df.col("data.id"), df.col("data.keyNote"), df.col("data.details"))
35
user6022341

Voici une fonction qui fait ce que vous voulez et qui peut gérer plusieurs colonnes imbriquées contenant des colonnes de même nom:

def flatten_df(nested_df):
    flat_cols = [c[0] for c in nested_df.dtypes if c[1][:6] != 'struct']
    nested_cols = [c[0] for c in nested_df.dtypes if c[1][:6] == 'struct']

    flat_df = nested_df.select(flat_cols +
                               [F.col(nc+'.'+c).alias(nc+'_'+c)
                                for nc in nested_cols
                                for c in nested_df.select(nc+'.*').columns])
    return flat_df

Avant:

root
 |-- x: string (nullable = true)
 |-- y: string (nullable = true)
 |-- foo: struct (nullable = true)
 |    |-- a: float (nullable = true)
 |    |-- b: float (nullable = true)
 |    |-- c: integer (nullable = true)
 |-- bar: struct (nullable = true)
 |    |-- a: float (nullable = true)
 |    |-- b: float (nullable = true)
 |    |-- c: integer (nullable = true)

Après:

root
 |-- x: string (nullable = true)
 |-- y: string (nullable = true)
 |-- foo_a: float (nullable = true)
 |-- foo_b: float (nullable = true)
 |-- foo_c: integer (nullable = true)
 |-- bar_a: float (nullable = true)
 |-- bar_b: float (nullable = true)
 |-- bar_c: integer (nullable = true)
3
steco

J'ai généralisé un peu plus la solution de stecos afin que l'aplatissement puisse être effectué sur plus de deux couches de struct:

def flatten_df(nested_df, layers):
    flat_cols = []
    nested_cols = []
    flat_df = []

    flat_cols.append([c[0] for c in nested_df.dtypes if c[1][:6] != 'struct'])
    nested_cols.append([c[0] for c in nested_df.dtypes if c[1][:6] == 'struct'])

    flat_df.append(nested_df.select(flat_cols[0] +
                               [col(nc+'.'+c).alias(nc+'_'+c)
                                for nc in nested_cols[0]
                                for c in nested_df.select(nc+'.*').columns])
                  )
    for i in range(1, layers):
        print (flat_cols[i-1])
        flat_cols.append([c[0] for c in flat_df[i-1].dtypes if c[1][:6] != 'struct'])
        nested_cols.append([c[0] for c in flat_df[i-1].dtypes if c[1][:6] == 'struct'])

        flat_df.append(flat_df[i-1].select(flat_cols[i] +
                                [col(nc+'.'+c).alias(nc+'_'+c)
                                    for nc in nested_cols[i]
                                    for c in flat_df[i-1].select(nc+'.*').columns])
        )

    return flat_df[-1]

il suffit d'appeler avec:

my_flattened_df = flatten_df(my_df_having_nested_structs, 3)

(Le second paramètre est le niveau de calques à aplatir, dans mon cas, c'est 3)

2
Aydin K.

Une méthode simple consiste à utiliser SQL. Vous pouvez créer une chaîne de requête SQL pour alias imbriquée dans la colonne sous forme de colonnes simples.

  1. Récupérer le schéma de trame de données (df.schema ()) 
  2. Transforme le schéma en SQL (Pour (champ: schema (). Champs ()) ....
  3. Requête "val newDF = SqlContext.sql (" SELECT "+ sqlGenerated +" FROM source ")

Un exemple en Java:

https://Gist.github.com/ebuildy/3de0e2855498e5358e4eed1a4f72ea48

(Je préfère le mode SQL, vous pouvez donc facilement le tester sur Spark-Shell et entre plusieurs langues).

1
Thomas Decaux