Lendo dados no formato JSON com pandas
Conheça funções úteis da biblioteca pandas para trabalhar com dados no formato JSON
Publicado por Giliard Godoi em Categoria: data-analysis. Tags: python, pandas, json
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.
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.
Podemos transformar (ou decodificar) esses dados em um dicionário Python utilizando a função json.loads. E magicamente, temos um dicionário.
import json
single_character = '''
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": [
"Radiation resistance",
"Turning tiny",
"Radiation blast"
]
}
'''
superhero = json.loads(single_character)
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:
- Decodificamos os dados em JSON em dicionários;
- Utilizamos a função
pd.json_normalizedefinindo os parâmetrosrecord_pathspara chave que contém os nossos principais dados, emetapara 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: