Как преобразовать список массивов Json с несколькими возможными значениями в столбцы в кадре данных, используя pyspark - PullRequest
0 голосов
/ 13 апреля 2019

Я использую API отчетов администратора Google через Python SDK в Databricks (Spark + Python 3.5).

Возвращает данные в следующем формате (код pyspark Databricks):

dbutils.fs.put("/tmp/test.json", '''{
    "userEmail": "rod@test.com", 
    "parameters": [
        {
            "intValue": "0",
            "name": "classroom:num_courses_created"
        },
        {
            "boolValue": true,
            "name": "accounts:is_disabled"
        },
        {
            "name": "classroom:role",
            "stringValue": "student"
        }
    ]
}''', True)

Есть 188 параметров, и для каждого параметра это может быть int, bool, date или string.В зависимости от типа поля Api возвращает значение в соответствующем значении (например, intValue для поля int и boolValue для логического значения).

Я записываю этот JSON нетронутым в мой набор данных и обрабатываю его позже, загружаяэто в искровой фрейм данных:

testJsonData = sqlContext.read.json("/tmp/test.json", multiLine=True)

В результате получается фрейм данных с такой схемой:

  • userEmail: строка
  • параметры: массив
    • element: struct
      • boolValue: логическое значение
      • intValue: строка
      • имя: строка
      • stringValue: строка

Если я отображаю кадр данных, он отображается как

{"boolValue": null, "intValue": "0", "name": "classroom: num_courses_created","stringValue": null} {"boolValue": true, "intValue": null, "name": "accounts: is_disabled", "stringValue": null}
{"boolValue": null, "intValue": null, "name": "classroom: role", "stringValue": "student"}

Как вы можете видеть, он имеет нулевые значения для несуществующих типов.

Конечное состояние, которое мне нужно, - это столбцы в кадре данных, например:

required result

, и поворотные столбцы будут напечатаны правильно (например, classroom: num_courses_created будеттипа int - см. желтые столбцы выше)

Вот что я пробовал до сих пор:

from pyspark.sql.functions import explode
tempDf = testJsonData.select("userEmail", explode("parameters").alias("parameters_exploded"))
explodedColsDf = tempDf.select("userEmail", "parameters_exploded.*")

Это приводит к кадру данных с этой схемой:

  • userEmail: строка
  • boolValue: логическое значение
  • intValue: строка
  • имя: строка
  • stringValue: строка

Iзатем переведите строки в столбцы на основе поля «Имя» («classroom: num_courses_created», «classroom: role» и т. д. (существует 188 пар параметров имя / значение):

#turn intValue into an Int column
explodedColsDf = explodedColsDf.withColumn("intValue", explodedColsDf.intValue.cast(IntegerType()))
pivotedDf = explodedColsDf.groupBy("userEmail").pivot("name").sum("intValue")

, что приводит кэтот фрейм данных:

  • userEmail: строка
  • account: is_disabled: long
  • classroom: num_courses_created: long
  • classroom: роль: long

, что неверно, так как типы столбцов неверны.

Что мне нужно сделать, это somehow посмотреть на все typeValues ​​для столбца параметра (нет способа узнать тип по имени или вывести его - кроме как в оригинальном Json, где он возвращает только значение typeValue, которое уместно), и все, что не равно NULL, являетсятип этого столбца.Каждый параметр появляется только один раз, поэтому для ключа электронной почты просто необходимо вывести значения строки, bool, int и date, а не агрегировать их.

Насколько мне известно, я подумал, что более простым решением может бытьвернитесь назад к началу и поверните столбцы за до Я выписываю Json, чтобы он был в том формате, который я хочу, когда я загружаю его обратно в Spark, однако я не хотел преобразовывать необработанные данныесовсем.Я также предпочел бы не кодировать схему для 188 полей вручную, так как я хочу динамически выбирать, какие поля мне нужны, поэтому он должен иметь возможность обрабатывать это.

1 Ответ

1 голос
/ 13 апреля 2019

Приведенный ниже код преобразует предоставленный пример JSON в кадр данных (без использования PySpark).

Библиотеки импорта

import numpy as np
import pandas as pd

Назначение переменных

true = True
false = False

Назначение JSON переменной

data = [{
"userEmail": "rod@test.com", 
"parameters": [
    {
        "intValue": "0",
        "name": "classroom:num_courses_created"
    },
    {
        "boolValue": true,
        "name": "accounts:is_disabled"
    },
    {
        "name": "classroom:role",
        "stringValue": "student"
    }
    ]
},
{
"userEmail": "EMAIL2@test.com", 
"parameters": [
    {
        "intValue": "1",
        "name": "classroom:num_courses_created"
    },
    {
        "boolValue": false,
        "name": "accounts:is_disabled"
    },
    {
        "name": "classroom:role",
        "stringValue": "student2"
    }
    ]
}

]

Функция для преобразования словаря в столбцы

def get_col(x):
    y = pd.DataFrame(x, index=[0])
    col_name = y.iloc[0]['name']
    y = y.drop(columns=['name'])
    y.columns = [col_name]
    return y

Итерация по списку JSON

df = pd.DataFrame()

for item in range(len(data)):

    # Initialize empty dataframe
    trow = pd.DataFrame()
    temp = pd.DataFrame(data[item])

    for i in range(temp.shape[0]):

        # Read each row
        x = temp.iloc[i]['parameters']
        trow = pd.concat([trow,get_col(x)], axis=1)
        trow['userEmail'] = temp.iloc[i]['userEmail']


    df = df.append(trow) 

# Rearrange columns, drop those that are not needed
df = df[['userEmail', 'classroom:num_courses_created', 'accounts:is_disabled', 'classroom:role']]

Выход:

enter image description here

......................... Предыдущее редактирование .....................

Преобразование JSON / вложенных словарей в кадр данных

temp = pd.DataFrame(data)

# Initialize empty dataframe
df = pd.DataFrame()
for i in range(temp.shape[0]):
    # Read each row
    x = temp.iloc[i]['parameters']
    temp1 = pd.DataFrame([x], columns=x.keys())
    temp1['userEmail'] = temp.iloc[i]['userEmail']

    # Convert nested key:value pairs
    y = x['name'].split(sep=':')
    temp1['name_' + y[0]] = y[1]

    # Combine to dataframe
    df = df.append(temp1, sort=False)

# Rearrange columns, drop those that are not needed
df = df[['userEmail', 'intValue', 'stringValue', 'boolValue', 'name_classroom', 'name_accounts']]

Вывод

enter image description here

Edit-1 На основе скриншота вобновленный вопрос, код ниже должен работать.

Назначение переменных

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...