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_normalize
definindo os parâmetrosrecord_paths
para chave que contém os nossos principais dados, emeta
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: