JSON转换为pandas DataFrame

245
我想要做的是从谷歌地图API中提取沿着由纬度和经度坐标指定的路径的高程数据,如下所示:
from urllib2 import Request, urlopen
import json

path1 = '42.974049,-81.205203|42.974298,-81.195755'
request=Request('http://maps.googleapis.com/maps/api/elevation/json?locations='+path1+'&sensor=false')
response = urlopen(request)
elevations = response.read()

这给我提供了一个类似于以下的数据:
elevations.splitlines()

['{',
 '   "results" : [',
 '      {',
 '         "elevation" : 243.3462677001953,',
 '         "location" : {',
 '            "lat" : 42.974049,',
 '            "lng" : -81.205203',
 '         },',
 '         "resolution" : 19.08790397644043',
 '      },',
 '      {',
 '         "elevation" : 244.1318664550781,',
 '         "location" : {',
 '            "lat" : 42.974298,',
 '            "lng" : -81.19575500000001',
 '         },',
 '         "resolution" : 19.08790397644043',
 '      }',
 '   ],',
 '   "status" : "OK"',
 '}']

当将其放入DataFrame中时,我得到以下内容:

enter image description here

pd.read_json(elevations)

以下是我想要的内容:

enter image description here

我不确定是否可行,但主要是想找一种将高程、纬度和经度数据放入pandas dataframe中的方法(不需要花哨的多行标题)。

如果有人能提供帮助或关于处理这些数据的建议,那就太好了!如果你还没看出来,我之前并没有接触过json数据...

编辑:

这种方法并不太吸引人,但似乎很有效:

data = json.loads(elevations)
lat,lng,el = [],[],[]
for result in data['results']:
    lat.append(result[u'location'][u'lat'])
    lng.append(result[u'location'][u'lng'])
    el.append(result[u'elevation'])
df = pd.DataFrame([lat,lng,el]).T

最终得到的数据框将具有纬度、经度和海拔列。

enter image description here


你好朋友,你知道如何获取一个 JSON 数据的某个子部分吗? - M. Mariscal
14个回答

305

我使用pandas 1.01中包含的json_normalize()快速且简便地解决了我想要的问题。

from urllib2 import Request, urlopen
import json

import pandas as pd    

path1 = '42.974049,-81.205203|42.974298,-81.195755'
request=Request('http://maps.googleapis.com/maps/api/elevation/json?locations='+path1+'&sensor=false')
response = urlopen(request)
elevations = response.read()
data = json.loads(elevations)
df = pd.json_normalize(data['results'])

这会生成一个很好的扁平化数据框,其中包含我从Google Maps API获得的JSON数据。


28
似乎这个方法不再有效 - 我不得不使用 pd.DataFrame.from_records(),就像这里描述的那样:https://dev59.com/V2Ij5IYBdhLWcg3wGRiM#33020669 - avv
4
有时候,如果 JSON 太复杂,使用 from_records 也无法正常工作,你需要使用 json_normalize 函数将其展平。可以查看此链接获取更多信息:https://dev59.com/bVkS5IYBdhLWcg3wamFy#39906235 - devssh

48

看一下这个片段。

# reading the JSON data using json.load()
file = 'data.json'
with open(file) as train_file:
    dict_train = json.load(train_file)

# converting json dataset from dictionary to dataframe
train = pd.DataFrame.from_dict(dict_train, orient='index')
train.reset_index(level=0, inplace=True)
希望它有所帮助 :)

1
错误。您应该将文件内容(即字符串)传递给json.loads(),而不是文件对象本身 - json.load(train_file.read())。 - Vasin Yuriy
如果 .json 文件出现问题,请将此片段的最后两行替换为以下答案:https://stackoverflow.com/questions/59695056/the-error-attributeerror-list-object-has-no-attribute-values-appears-when - liorr
1
我来了解orient='index'选项。非常感谢。 - Athachai

35

接受答案的优化:

接受的答案存在一些功能问题,因此我想分享我的代码,它不依赖于urllib2:

import requests
from pandas import json_normalize
url = 'https://www.energidataservice.dk/proxy/api/datastore_search?resource_id=nordpoolmarket&limit=5'

response = requests.get(url)
dictr = response.json()
recs = dictr['result']['records']
df = json_normalize(recs)
print(df)

输出:

        _id                    HourUTC               HourDK  ... ElbasAveragePriceEUR  ElbasMaxPriceEUR  ElbasMinPriceEUR
0    264028  2019-01-01T00:00:00+00:00  2019-01-01T01:00:00  ...                  NaN               NaN               NaN
1    138428  2017-09-03T15:00:00+00:00  2017-09-03T17:00:00  ...                33.28              33.4              32.0
2    138429  2017-09-03T16:00:00+00:00  2017-09-03T18:00:00  ...                35.20              35.7              34.9
3    138430  2017-09-03T17:00:00+00:00  2017-09-03T19:00:00  ...                37.50              37.8              37.3
4    138431  2017-09-03T18:00:00+00:00  2017-09-03T20:00:00  ...                39.65              42.9              35.3
..      ...                        ...                  ...  ...                  ...               ...               ...
995  139290  2017-10-09T13:00:00+00:00  2017-10-09T15:00:00  ...                38.40              38.4              38.4
996  139291  2017-10-09T14:00:00+00:00  2017-10-09T16:00:00  ...                41.90              44.3              33.9
997  139292  2017-10-09T15:00:00+00:00  2017-10-09T17:00:00  ...                46.26              49.5              41.4
998  139293  2017-10-09T16:00:00+00:00  2017-10-09T18:00:00  ...                56.22              58.5              49.1
999  139294  2017-10-09T17:00:00+00:00  2017-10-09T19:00:00  ...                56.71              65.4              42.2 

注意:API 是丹麦电力价格相关的。


2
这个解决方案更好,因为你可以专注于pandas包而不需要导入其他包。 - KY Lu

19

只是一个已被接受的答案的新版本,因为 python3.x 不支持 urllib2

from requests import request
import json
from pandas.io.json import json_normalize

path1 = '42.974049,-81.205203|42.974298,-81.195755'
response=request(url='http://maps.googleapis.com/maps/api/elevation/json?locations='+path1+'&sensor=false', method='get')
elevations = response.json()
elevations
data = json.loads(elevations)
json_normalize(data['results'])

18
你可以先将你的JSON数据导入Python字典中:

你可以先将你的JSON数据导入Python字典中:

data = json.loads(elevations)

然后动态修改数据:

for result in data['results']:
    result[u'lat']=result[u'location'][u'lat']
    result[u'lng']=result[u'location'][u'lng']
    del result[u'location']

重建json字符串:

elevations = json.dumps(data)

最后:

pd.read_json(elevations)

你也可以避免将数据转换为字符串,我猜Pandas可以直接从字典创建一个DataFrame(我已经很久没用过它了:p)


我仍然使用json数据和创建的字典得到相同的结果。似乎数据框中的每个元素都有自己的字典。我尝试在迭代“data”时以不太吸引人的方式构建单独的lat、lng和elevation列表,使用了您的方法。 - pbreach
@user2593236:你好,我在 SO 上复制/粘贴代码时出现了错误:缺少一个 del(已编辑答案)。 - Raphaël Braud
嗯...仍然是同样的问题,其中'results'和'status'作为标题,而其余的json数据出现在每个单元格中。我认为解决这个问题的方法是改变数据的格式,使其不再细分为“结果”和“状态”,然后数据框将使用“lat”、“lng”、“elevation”、“resolution”作为单独的标题。要么这样,要么我需要找到一种方法将json数据加载到具有多级标题索引的数据框中,就像我在问题中提到的那样。 - pbreach
你期望哪个最终表格?是你编辑之后得到的那个吗? - Raphaël Braud
我最终编辑后得到的这个程序可以胜任这项工作,基本上我所需要的就是将数据以表格形式获取并导出,方便我进行进一步处理。 - pbreach

12
使用Json加载文件并使用DataFrame.from_dict函数将其转换为Pandas数据框。
import json
import pandas as pd
json_string = '{ "name":"John", "age":30, "car":"None" }'

a_json = json.loads(json_string)
print(a_json)

dataframe = pd.DataFrame.from_dict(a_json)

8

这是一个小实用类,可以将JSON转换为DataFrame并进行反向操作:希望您会发现这很有帮助。

# -*- coding: utf-8 -*-
from pandas.io.json import json_normalize

class DFConverter:

    #Converts the input JSON to a DataFrame
    def convertToDF(self,dfJSON):
        return(json_normalize(dfJSON))

    #Converts the input DataFrame to JSON 
    def convertToJSON(self, df):
        resultJSON = df.to_json(orient='records')
        return(resultJSON)

5
问题在于您的数据框中有几列包含内部嵌套字典的字典。 有用的JSON通常是嵌套很深的。 我已经编写了小函数,将我想要的信息提取到一个新列中。 这样我就可以按照我想要使用的格式进行操作。
for row in range(len(data)):
    #First I load the dict (one at a time)
    n = data.loc[row,'dict_column']
    #Now I make a new column that pulls out the data that I want.
    data.loc[row,'new_column'] = n.get('key')

根据 @niltoid 的建议,我在2014年编写此代码时可能使用了较旧版本的Pandas。 Pandas已更改了.loc.iloc的用法,您可能需要进行调整。请参见下面的调整。 - billmanH

1
#Use the small trick to make the data json interpret-able
#Since your data is not directly interpreted by json.loads()

>>> import json
>>> f=open("sampledata.txt","r+")
>>> data = f.read()
>>> for x in data.split("\n"):
...     strlist = "["+x+"]"
...     datalist=json.loads(strlist)
...     for y in datalist:
...             print(type(y))
...             print(y)
...
...
<type 'dict'>
{u'0': [[10.8, 36.0], {u'10': 0, u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}
<type 'dict'>
{u'1': [[10.8, 36.1], {u'10': 0, u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}
<type 'dict'>
{u'2': [[10.8, 36.2], {u'10': 0, u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}
<type 'dict'>
{u'3': [[10.8, 36.300000000000004], {u'10': 0, u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}
<type 'dict'>
{u'4': [[10.8, 36.4], {u'10': 0, u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}
<type 'dict'>
{u'5': [[10.8, 36.5], {u'10': 0, u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}
<type 'dict'>
{u'6': [[10.8, 36.6], {u'10': 0, u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}
<type 'dict'>
{u'7': [[10.8, 36.7], {u'10': 0, u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}
<type 'dict'>
{u'8': [[10.8, 36.800000000000004], {u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}
<type 'dict'>
{u'9': [[10.8, 36.9], {u'1': 0, u'0': 0, u'3': 0, u'2': 0, u'5': 0, u'4': 0, u'7': 0, u'6': 0, u'9': 0, u'8': 0}]}



1
一旦您获得了由接受的答案得到的扁平化的DataFrame,您可以像这样将列变成MultiIndex(“花式多行表头”):
df.columns = pd.MultiIndex.from_tuples([tuple(c.split('.')) for c in df.columns])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接