Giliard Godoi

Machine Learning. Análise e visualização de dados. Inteligência Artificial. Computação Evolutiva. Algoritmos Genéticos. Otimização combinatória. Teoria dos Grafos. Pythonista.

Lendo dados no formato JSON com pandas


O que é JSON?

JavaScript Object Notation (JSON) é uma representação de dados baseado em chave-valor, seguindo a sintaxe dos objetos da linguagem JavaScript. Popularizou-se como uma forma de transmissão de dados pela Web, como uma alternativa à estrutura XML.

Outras linguagens também oferecem suporte para se trabalhar com dados nesse formato. Por exemplo, a biblioteca nativa json permite converter strings no formato JSON para dicionários da linguagem Python.

Para que essa conversão seja possível, é necessário estabelecer uma relação entre os tipos primitivos dessas duas linguagens. Essa correspondência é bastante simples pois ambas as linguagem oferecem um tipo primitivo para strings (texto), valores numéricos (inteiros e floats), tipos booleanos (True, False) e listas, dicionários e o valor nulo (Null no caso do JavaScript e None no caso do Python).

Nesse artigo, veremos como transformar dados em JSON em dataframes em quatro exemplos. - Um exemplo básico; - Um exemplo intermediário - Um exemplo avançado; - E um exemplo um pouco mais avançado.

O exemplo de dados no formato JSON utilizado nesse artigo é adaptado daquele presente na página de documentação da Mozilla Web Docs, e que pode ser consultado nesse link.

Exemplo básico

Nesse primeiro exemplo, transformaremos a string no formato JSON em um dicionário em Python utilizando a biblioteca nativa json.

import json

A variável single_character é string com um único objeto no formato JSON. Esse objeto temos as chaves 'name', 'age', 'secretIdentity' e 'powers', e seus respectivos valores.

Note que o valor correspondente a chave 'powers' é uma lista de strings. O uso de listas (ou arrays do JavaScript) codifica uma relação de um-para-muitos. No caso, um super-heroí possui vários poderes.

single_character = '''
{
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": [
        "Radiation resistance",
        "Turning tiny",
        "Radiation blast"
      ]
    }
'''

superhero = json.loads(single_character)

Podemos transformar (ou decodificar) esses dados em um dicionário Python utilizando a função json.loads. E magicamente, temos um dicionário.

superhero
{'name': 'Molecule Man',
 'age': 29,
 'secretIdentity': 'Dan Jukes',
 'powers': ['Radiation resistance', 'Turning tiny', 'Radiation blast']}

Um pandas Dataframe pode ser construído a partir de uma lista de dicionários. Mas se passarmos esse simples dicionário superhero para o construtor do dataframe, também obtemos um dataframe.

import pandas as pd
pd.DataFrame(superhero)
name age secretIdentity powers
0 Molecule Man 29 Dan Jukes Radiation resistance
1 Molecule Man 29 Dan Jukes Turning tiny
2 Molecule Man 29 Dan Jukes Radiation blast

Note que o atributo powers, que codifica uma lista de três poderes, e ele se desdobra em três registros (linhas).

Porém, esse mesmo resultado pode ser obtido utilizando a função pd.read_json

pd.read_json(single_character)
name age secretIdentity powers
0 Molecule Man 29 Dan Jukes Radiation resistance
1 Molecule Man 29 Dan Jukes Turning tiny
2 Molecule Man 29 Dan Jukes Radiation blast

Exemplo Intermediário

Nesse próximo exemplo, a variável squad é uma lista de heróis, e ainda temos o mesmo atributo powers com uma lista de poderes para esses heróis.

squad = '''
 [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": [
        "Radiation resistance",
        "Turning tiny",
        "Radiation blast"
      ]
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers": [
        "Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"
      ]
    },
    {
      "name": "Eternal Flame",
      "age": 1000000,
      "secretIdentity": "Unknown",
      "powers": [
        "Immortality",
        "Heat Immunity",
        "Inferno",
        "Teleportation",
        "Interdimensional travel"
      ]
    }
  ]
'''

Nesse caso função pd.read_json não desdobra os valores da coluna powers. Os valores dessa coluna são diversas listas.

pd.read_json(squad)
name age secretIdentity powers
0 Molecule Man 29 Dan Jukes [Radiation resistance, Turning tiny, Radiation…
1 Madame Uppercut 39 Jane Wilson [Million tonne punch, Damage resistance, Super…
2 Eternal Flame 1000000 Unknown [Immortality, Heat Immunity, Inferno, Teleport…

Caso se queira desdobrar esse relacionamento um-para-vários, é possível utilizar o método explode.

pd.read_json(squad).explode('powers')
name age secretIdentity powers
0 Molecule Man 29 Dan Jukes Radiation resistance
0 Molecule Man 29 Dan Jukes Turning tiny
0 Molecule Man 29 Dan Jukes Radiation blast
1 Madame Uppercut 39 Jane Wilson Million tonne punch
1 Madame Uppercut 39 Jane Wilson Damage resistance
1 Madame Uppercut 39 Jane Wilson Superhuman reflexes
2 Eternal Flame 1000000 Unknown Immortality
2 Eternal Flame 1000000 Unknown Heat Immunity
2 Eternal Flame 1000000 Unknown Inferno
2 Eternal Flame 1000000 Unknown Teleportation
2 Eternal Flame 1000000 Unknown Interdimensional travel

Exemplo Avançado

No próximo exemplo, os temos as informações do esquadrão de super-heróis: nomes, cidade, ano de formação, base secreta, além da relação de membros.

Novamente temos uma relação de um-para-muitos: um esquadrão e vários super-heróis. Nesse exemplo, vamos omitir a relação um-para-muitos entre heróis e poderes.

squad_advanced = '''{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers" : "molecule powers"
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers" : "badass"
    },
    {
      "name": "Eternal Flame",
      "age": 1000000,
      "secretIdentity": "Unknown",
      "powers" : "flame"
    }
  ]
}
'''

O dataframe resultante da função pd.read_json possui os atributos do esquadrão nome, cidade, ano de formação, base secreta e se está ou não em atividade.

Os valores da coluna members no entanto, são dicionários correspondentes aos super-heróis.

pd.read_json(squad_advanced)
squadName homeTown formed secretBase active members
0 Super hero squad Metro City 2016 Super tower True {‘name’: ‘Molecule Man’, ‘age’: 29, ‘secretIde…
1 Super hero squad Metro City 2016 Super tower True {‘name’: ‘Madame Uppercut’, ‘age’: 39, ‘secret…
2 Super hero squad Metro City 2016 Super tower True {‘name’: ‘Eternal Flame’, ‘age’: 1000000, ‘sec…

Ao utilizar o método explode para desdobrar essa relação os valores da coluna members corresponderá às chaves dos dicionários.

Muito provavelmente, esse não é o resultado esperado para as nossas análises, conforme podemos ver.

pd.read_json(squad_advanced).explode('members')
squadName homeTown formed secretBase active members
0 Super hero squad Metro City 2016 Super tower True name
0 Super hero squad Metro City 2016 Super tower True age
0 Super hero squad Metro City 2016 Super tower True secretIdentity
0 Super hero squad Metro City 2016 Super tower True powers
1 Super hero squad Metro City 2016 Super tower True name
1 Super hero squad Metro City 2016 Super tower True age
1 Super hero squad Metro City 2016 Super tower True secretIdentity
1 Super hero squad Metro City 2016 Super tower True powers
2 Super hero squad Metro City 2016 Super tower True name
2 Super hero squad Metro City 2016 Super tower True age
2 Super hero squad Metro City 2016 Super tower True secretIdentity
2 Super hero squad Metro City 2016 Super tower True powers

Nesses casos podemos utilizar a função pd.json_normalize para obter melhores resultados.

Contudo, essa função aceita dicionários Python, por isso é necessário utilizar a função json.loads para decodificar os dados JSON.

Essa função recebe o parâmetro record_paths que indica qual é o atributo principal dos dados, nesse caso o atributo members com informações dos nossos três super-heróis.

squad_dict = json.loads(squad_advanced)

pd.json_normalize(squad_dict, record_path='members')
name age secretIdentity powers
0 Molecule Man 29 Dan Jukes molecule powers
1 Madame Uppercut 39 Jane Wilson badass
2 Eternal Flame 1000000 Unknown flame

Para manter os atributos que se refere às informações do esquadrão, utilizamos os parâmetros meta e passamos a lista de atributos que queremos manter.

Note que os valores desses atributos são repetidos para cada um dos três super-heróis.

pd.json_normalize(squad_dict, 
                  record_path='members', 
                  meta=['squadName', 'homeTown', 'secretBase'])
name age secretIdentity powers squadName homeTown secretBase
0 Molecule Man 29 Dan Jukes molecule powers Super hero squad Metro City Super tower
1 Madame Uppercut 39 Jane Wilson badass Super hero squad Metro City Super tower
2 Eternal Flame 1000000 Unknown flame Super hero squad Metro City Super tower

Um exemplo um pouco mais avançado

Nesse último caso, vamos unir o que aprendemos nos dois últimos exemplos. Para tanto, voltamos com a relação um-para-vários presente no atributo powers.

squad_more_advanced = '''
{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": [
        "Radiation resistance",
        "Turning tiny",
        "Radiation blast"
      ]
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers": [
        "Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"
      ]
    },
    {
      "name": "Eternal Flame",
      "age": 1000000,
      "secretIdentity": "Unknown",
      "powers": [
        "Immortality",
        "Heat Immunity",
        "Inferno",
        "Teleportation",
        "Interdimensional travel"
      ]
    }
  ]
}
'''

Então, seguimos os mesmos passos:

  1. Decodificamos os dados em JSON em dicionários;
  2. Utilizamos a função pd.json_normalize definindo os parâmetros record_paths para chave que contém os nossos principais dados, e meta para aquelas chaves que queremos manter;
squad_dict_2 = json.loads(squad_more_advanced)

df = pd.json_normalize(squad_dict_2, 
                  record_path='members', 
                  meta=['squadName', 'homeTown', 'secretBase'])

df
name age secretIdentity powers squadName homeTown secretBase
0 Molecule Man 29 Dan Jukes [Radiation resistance, Turning tiny, Radiation… Super hero squad Metro City Super tower
1 Madame Uppercut 39 Jane Wilson [Million tonne punch, Damage resistance, Super… Super hero squad Metro City Super tower
2 Eternal Flame 1000000 Unknown [Immortality, Heat Immunity, Inferno, Teleport… Super hero squad Metro City Super tower

A coluna que corresponde a chave powers, que mantém informações sobre os poderes dos super-heróis, mantém listas com nomes dos poderes de cada super-herói.

Poderíamos desdobrar os valores dessa coluna utilizando o método explode, conforme pode-se ver a seguir.

df.explode('powers')
name age secretIdentity powers squadName homeTown secretBase
0 Molecule Man 29 Dan Jukes Radiation resistance Super hero squad Metro City Super tower
0 Molecule Man 29 Dan Jukes Turning tiny Super hero squad Metro City Super tower
0 Molecule Man 29 Dan Jukes Radiation blast Super hero squad Metro City Super tower
1 Madame Uppercut 39 Jane Wilson Million tonne punch Super hero squad Metro City Super tower
1 Madame Uppercut 39 Jane Wilson Damage resistance Super hero squad Metro City Super tower
1 Madame Uppercut 39 Jane Wilson Superhuman reflexes Super hero squad Metro City Super tower
2 Eternal Flame 1000000 Unknown Immortality Super hero squad Metro City Super tower
2 Eternal Flame 1000000 Unknown Heat Immunity Super hero squad Metro City Super tower
2 Eternal Flame 1000000 Unknown Inferno Super hero squad Metro City Super tower
2 Eternal Flame 1000000 Unknown Teleportation Super hero squad Metro City Super tower
2 Eternal Flame 1000000 Unknown Interdimensional travel Super hero squad Metro City Super tower

Conclusão

JSON permite estruturar dados nos mais diferentes formatos. Atributos podem manter listas de valores, ou até mesmo lista de dicionários. As possibilidades são numerosas.

Nesse artigo, comentamos sobre o uso de algumas funções e métodos para transformados dados no formato JSON em dataframes, sendo elas:

  1. json.loads
  2. pd.read_json
  3. dataframe.explode
  4. pd.json_normalize