web-dev-qa-db-fra.com

Pyspark: Analyser une colonne de chaînes JSON

J'ai une base de données pyspark composée d'une colonne, appelée json, où chaque ligne est une chaîne unicode de json. Je voudrais analyser chaque ligne et renvoyer une nouvelle image de données où chaque ligne est le json analysé.

# Sample Data Frame
jstr1 = u'{"header":{"id":12345,"foo":"bar"},"body":{"id":111000,"name":"foobar","sub_json":{"id":54321,"sub_sub_json":{"col1":20,"col2":"somethong"}}}}'
jstr2 = u'{"header":{"id":12346,"foo":"baz"},"body":{"id":111002,"name":"barfoo","sub_json":{"id":23456,"sub_sub_json":{"col1":30,"col2":"something else"}}}}'
jstr3 = u'{"header":{"id":43256,"foo":"foobaz"},"body":{"id":20192,"name":"bazbar","sub_json":{"id":39283,"sub_sub_json":{"col1":50,"col2":"another thing"}}}}'
df = sql_context.createDataFrame([Row(json=jstr1),Row(json=jstr2),Row(json=jstr3)])

J'ai essayé de mapper chaque ligne avec json.loads:

(df
  .select('json')
  .rdd
  .map(lambda x: json.loads(x))
  .toDF()
).show()

Mais cela retourne un TypeError: expected string or buffer

Je suppose que le problème provient en partie du fait que, lors de la conversion d'un dataframe en un rdd, les informations de schéma sont perdues. J'ai donc également essayé de saisir manuellement les informations de schéma:

schema = StructType([StructField('json', StringType(), True)])
rdd = (df
  .select('json')
  .rdd
  .map(lambda x: json.loads(x))
)
new_df = sql_context.createDataFrame(rdd, schema)
new_df.show()

Mais je reçois le même TypeError.

En regardant cette réponse , il pourrait sembler utile d’aplanir les lignes avec flatMap, mais je n’en ai pas le succès non plus:

schema = StructType([StructField('json', StringType(), True)])
rdd = (df
  .select('json')
  .rdd
  .flatMap(lambda x: x)
  .flatMap(lambda x: json.loads(x))
  .map(lambda x: x.get('body'))
)
new_df = sql_context.createDataFrame(rdd, schema)
new_df.show()

Je reçois cette erreur: AttributeError: 'unicode' object has no attribute 'get'.

21
Steve

Convertir une structure de données avec des chaînes json en structure de données structurée est en fait assez simple dans spark si vous convertissez la structure de données en RDD de chaînes avant (voir: http: //spark.Apache .org/docs/latest/sql-programming-guide.html # json-datasets )

Par exemple:

>>> new_df = sql_context.read.json(df.rdd.map(lambda r: r.json))
>>> new_df.printSchema()
root
 |-- body: struct (nullable = true)
 |    |-- id: long (nullable = true)
 |    |-- name: string (nullable = true)
 |    |-- sub_json: struct (nullable = true)
 |    |    |-- id: long (nullable = true)
 |    |    |-- sub_sub_json: struct (nullable = true)
 |    |    |    |-- col1: long (nullable = true)
 |    |    |    |-- col2: string (nullable = true)
 |-- header: struct (nullable = true)
 |    |-- foo: string (nullable = true)
 |    |-- id: long (nullable = true)
23
Mariusz

Pour Spark 2.1 + , vous pouvez utiliser from_json qui permet la conservation des autres colonnes non json dans le cadre de données comme suit:

from pyspark.sql.functions import from_json, col
json_schema = spark.read.json(df.rdd.map(lambda row: row.json)).schema
df.withColumn('json', from_json(col('json'), json_schema))

Vous laissez Spark dériver le schéma de la colonne de chaîne json. Ensuite, le df.jsoncolumn n'est plus un StringType, mais la structure json correctement décodée, c'est-à-dire imbriquée StrucType et toutes les autres colonnes de df sont conservées telles quelles.

Vous pouvez accéder au contenu JSON comme suit:

df.select(col('json.header').alias('header'))
27
Martin Tapp

Les réponses existantes ne fonctionnent pas si votre JSON est autre chose que parfaitement/traditionnellement formaté. Par exemple, l'inférence de schéma basée sur RDD attend JSON entre accolades {} et fournira un schéma incorrect (résultant en null valeurs) si, par exemple, vos données ressemblent à:

[
  {
    "a": 1.0,
    "b": 1
  },
  {
    "a": 0.0,
    "b": 2
  }
]

J'ai écrit une fonction pour contourner ce problème en nettoyant JSON de telle sorte qu'il réside dans un autre objet JSON:

def parseJSONCols(df, *cols, sanitize=True):
    """Auto infer the schema of a json column and parse into a struct.

    rdd-based schema inference works if you have well-formatted JSON,
    like ``{"key": "value", ...}``, but breaks if your 'JSON' is just a
    string (``"data"``) or is an array (``[1, 2, 3]``). In those cases you
    can fix everything by wrapping the data in another JSON object
    (``{"key": [1, 2, 3]}``). The ``sanitize`` option (default True)
    automatically performs the wrapping and unwrapping.

    The schema inference is based on this
    `SO Post <https://stackoverflow.com/a/45880574)/>`_.

    Parameters
    ----------
    df : pyspark dataframe
        Dataframe containing the JSON cols.
    *cols : string(s)
        Names of the columns containing JSON.
    sanitize : boolean
        Flag indicating whether you'd like to sanitize your records
        by wrapping and unwrapping them in another JSON object layer.

    Returns
    -------
    pyspark dataframe
        A dataframe with the decoded columns.
    """
    res = df
    for i in cols:

        # sanitize if requested.
        if sanitize:
            res = (
                res.withColumn(
                    i,
                    psf.concat(psf.lit('{"data": '), i, psf.lit('}'))
                )
            )
        # infer schema and apply it
        schema = spark.read.json(res.rdd.map(lambda x: x[i])).schema
        res = res.withColumn(i, psf.from_json(psf.col(i), schema))

        # unpack the wrapped object if needed
        if sanitize:
            res = res.withColumn(i, psf.col(i).data)
    return res

Remarque: psf = pyspark.sql.functions.

8
Nolan Conaway

Voici une version concise (spark SQL) de la fonction parseJSONCols de @ nolan-conaway.

SELECT 
explode(
    from_json(
        concat('{"data":', 
               '[{"a": 1.0,"b": 1},{"a": 0.0,"b": 2}]', 
               '}'), 
        'data array<struct<a:DOUBLE, b:INT>>'
    ).data) as data;

PS J'ai aussi ajouté la fonction exploser: P

Vous aurez besoin de connaître certains types Hive SQL

0
Buthetleon