#python #json #pandas #dataframe #normalization
#python #json #pandas #фрейм данных #нормализация
Вопрос:
Справочная информация —
У меня есть ответ JSON от вызова API, который я пытаюсь сохранить в фрейме данных pandas, сохраняя при этом ту же структуру, что и при просмотре в системе, из которой я вызвал данные.
Функции, вызывающие ответ JSON —
def api_call():
вызывает API (примечание: url_list
в настоящее время содержит только 1 URL-адрес) и сохраняет ответ в api_response
переменной, используя json.loads(response.text)
def api_call():
url_list = url_constructor()
for url in url_list:
response = requests.get(url_list[0], auth = HTTPBasicAuth(key, secret), headers={"Firm":"583"})
api_response = json.loads(response.text)
return api_response
Функция, которая сохраняет ответ в файл, а также возвращает его:
def response_writer():
сохраняется api_response
как файл JSON. Он также возвращается api_response
.
def response_writer():
api_response = api_call()
timestr = datetime.datetime.now().strftime("%Y-%m-%d-%H:%M")
filename = 'api_response_' timestr '.json'
with open(filename, 'w') as output_data:
json.dump(api_response, output_data)
print("-------------------------------------------------------n",
"API RESPONSE SAVED:", filename, "n-------------------------------------------------------")
return api_response
Ответ в формате JSON —
{
"meta": {
"columns": [
{
"key": "node_id",
"display_name": "Entity ID",
"output_type": "Word"
},
{
"key": "bottom_level_holding_account_number",
"display_name": "Holding Account Number",
"output_type": "Word"
},
{
"key": "value",
"display_name": "Adjusted Value (USD)",
"output_type": "Number",
"currency": "USD"
},
{
"key": "node_ownership",
"display_name": "% Ownership",
"output_type": "Percent"
},
{
"key": "model_type",
"display_name": "Model Type",
"output_type": "Word"
},
{
"key": "valuation",
"display_name": "Valuation (USD)",
"output_type": "Number",
"currency": "USD"
},
{
"key": "_custom_jb_custodian_305769",
"display_name": "JB Custodian",
"output_type": "Word"
},
{
"key": "top_level_owner",
"display_name": "Top Level Owner",
"output_type": "Word"
},
{
"key": "top_level_legal_entity",
"display_name": "Top Level Legal Entity",
"output_type": "Word"
},
{
"key": "direct_owner",
"display_name": "Direct Owner",
"output_type": "Word"
},
{
"key": "online_status",
"display_name": "Online Status",
"output_type": "Word"
},
{
"key": "financial_service",
"display_name": "Financial Service",
"output_type": "Word"
},
{
"key": "_custom_placeholder_461415",
"display_name": "Placeholder or Fee Basis",
"output_type": "Boolean"
},
{
"key": "_custom_close_date_411160",
"display_name": "Account Close Date",
"output_type": "Date"
},
{
"key": "_custom_ownership_audit_note_425843",
"display_name": "Ownership Audit Note",
"output_type": "Word"
}
],
"groupings": [
{
"key": "holding_account",
"display_name": "Holding Account"
}
]
},
"data": {
"type": "portfolio_views",
"attributes": {
"total": {
"name": "Total",
"columns": {
"direct_owner": null,
"node_ownership": null,
"online_status": null,
"_custom_ownership_audit_note_425843": null,
"model_type": null,
"_custom_placeholder_461415": null,
"top_level_owner": null,
"_custom_close_date_411160": null,
"valuation": null,
"bottom_level_holding_account_number": null,
"_custom_jb_custodian_305769": null,
"financial_service": null,
"top_level_legal_entity": null,
"value": null,
"node_id": null
},
"children": [
{
"entity_id": 4754837,
"name": "Apple Holdings Adv (748374923)",
"grouping": "holding_account",
"columns": {
"direct_owner": "Apple Holdings LLC",
"node_ownership": 1,
"online_status": "Online",
"_custom_ownership_audit_note_425843": null,
"model_type": "Holding Account",
"_custom_placeholder_461415": false,
"top_level_owner": "Forsyth Family",
"_custom_close_date_411160": null,
"valuation": 10423695.609450001,
"bottom_level_holding_account_number": "748374923",
"_custom_jb_custodian_305769": "Laverockbank",
"financial_service": "laverockbankcustodianservice",
"top_level_legal_entity": "Apple Holdings LLC",
"value": 10423695.609450001,
"node_id": "4754837"
},
}
]
}
}
},
"included": []
}
Ожидаемая структура JSON в фрейме данных Pandas —
Это структура, которую я пытаюсь передать в моем фрейме данных pandas —
| Holding Account | Entity ID | Holding Account Number | Adjusted Value (USD) | % Ownership | Model Type | Valuation (USD) | JB Custodian | Top Level Owner | Top Level Legal Entity | Direct Owner | Online Status | Financial Service | Placeholder or Fee Basis | Account Close Date | Ownership Audit Note |
|---------------------------------|-----------|------------------------|----------------------|-------------|-----------------|-----------------|--------------|-----------------|---------------------------------|---------------------------------|---------------|---------------------|--------------------------|--------------------|----------------------|
| Apple Holdings Adv (748374923) | 4754837 | 748374923 | $10,423,695.06 | 100.00% | Holding Account | $10,423,695.06 | BRF | Forsyth Family | Apple Holdings Partners LLC | Apple Holdings Partners LLC | Online | custodianservice | No | - | - |
Моя интерпретация структуры JSON —
Похоже, мне нужно сосредоточиться на {'columns:
(который имеет заголовки столбцов) и 'children'
(которые представляют строки данных, в моем случае, только 1x строку) 'data':
. Я могу проигнорировать 'groupings': [{'key': 'holding_account', 'display_name': 'Holding Account'}]},
, поскольку именно так в конечном счете сортируются данные в системе.
Есть ли у кого-нибудь советы о том, как я мог бы взять JSON и загрузить в фрейм данных с продемонстрированной структурой?
Моя интерпретация заключается в том, что мне нужно установить display_names
[ columns
] в качестве заголовков, а затем сопоставить соответствующие children
значения под каждым соответствующим display_names
заголовком / . Обычно их было бы больше children
(представляющих каждую строку данных для моего фрейма данных), однако я удалил все, кроме 1x, чтобы упростить интерпретацию.
Комментарии:
1. Проверьте Glom ( pypi.org/project/glom ), который может помочь вам отформатировать данные в необходимый формат, а затем использовать Pandas для создания из них фрейма данных.
Ответ №1:
Я не уверен, что это лучший способ распаковать словарь, но он работает:
(Он используется для хранения дочерних «метаданных», таких как идентификатор (дубликат) и полное имя учетной записи)
def unpack_dict(item, out):
for k, v in item.items():
if type(v) == dict:
unpack_dict(v, out)
else:
out[k] = v
return out
Теперь нам нужно использовать это для каждого дочернего элемента, чтобы получить данные
Из вашего примера кажется, что вы хотите сохранить учетную запись хранения (от дочернего элемента), но вам не нужен entity_id, поскольку он дублируется в node_id ?
Не уверен, поэтому я просто включу все столбцы с их «необработанными» именами
columns = unpack_dict(res["data"]["attributes"]["total"]["children"][0]
children = res["data"]["attributes"]["total"]["children"]
data = []
for i in children:
data.append(list(unpack_dict(i, {}).values()))
И создание фрейма данных из этого:
>>> pd.DataFrame(data=data, columns = columns)
entity_id name ... value node_id
0 4754837 Apple Holdings Adv (748374923) ... 1.042370e 07 4754837
[1 rows x 18 columns]
Теперь это можно изменить, чтобы иметь отображаемые имена вместо этих необработанных. Возможно, вам потребуется удалить некоторые столбцы, хотя, как я упоминал выше, идентификатор дублируется, вы упомянули группировку и т. Д.
Если вы имеете дело с большими объемами данных (тысячи записей), и для их анализа требуется много времени, избыточные столбцы можно удалить перед вставкой, data
чтобы сэкономить некоторое время позже.
Чтобы переименовать столбцы с помощью dict
:
df.rename(columns={'oldName1': 'newName1', 'oldName2': 'newName2'})
Комментарии:
1. Большое спасибо за ваш вклад. Ваше объяснение имеет смысл, однако можете ли вы дать контекст о том, как интегрировать эту функцию в мой код? Ответ API хранится в переменной с именем ‘api_response’ Примечание: у меня есть inc. функции, которые вызывают и сохраняют ответ API в моем вопросе.
2. @William правильно, вместо того, чтобы использовать
res
use yourapi_response
, и это должно сработать, поскольку этоdict
3. Спасибо за подтверждение. Кроме того, как это должно быть структурировано, т.Е. Размещение функции ‘def unpack_dict (item, out):’ и последующего кода, который вы упомянули? Я просто пытаюсь понять, как собрать все воедино, чтобы я мог изучить и понять, как это работает, и, конечно, протестировать!
4. @William да, вы можете просто поместить его один за другим, а затем с финалом
pd.DataFrame(data=data, columns = columns)
вы получите таблицу фреймов данных
Ответ №2:
Я предлагаю использовать pd.json_normalize()
( https://pandas.pydata.org/pandas-docs/version/1.2.0/reference/api/pandas.json_normalize.html ), который помогает преобразовать данные JSON в фрейм данных pandas.
Примечание 1: Далее я предполагаю, что данные доступны в вызываемом словаре python data
. Для целей тестирования я использовал
import json
json_data = '''
{
"meta": {
# ....
},
#...
"included": []
}
'''
data = json.loads(json_data)
где json_data
ваш ответ JSON. Поскольку json.loads()
он не принимает последнюю запятую, я опустил запятую после дочернего объекта.
pd.json_normalize()
предлагает различные варианты. Одна из возможностей — просто прочитать все «дочерние» данные, а затем удалить столбцы, которые не требуются. Кроме того, после нормализации некоторые столбцы имеют префикс «столбцы». который необходимо удалить.
import pandas as pd
df = pd.json_normalize(data['data']['attributes']['total']['children'])
df.drop(columns=['grouping', 'entity_id'], inplace=True)
df.columns = df.columns.str.replace(r'columns.', '')
Наконец, имена столбцов необходимо заменить именами в данных «столбцов»:
column_name_mapper = {column['key']: column['display_name'] for column in data['meta']['columns']}
df.rename(columns=column_name_mapper, inplace=True)
Примечание 2: Есть некоторые незначительные отклонения от ожидаемой структуры, которую вы описали. Наиболее примечательно, что слово «name» (со значением строки «Apple Holdings Adv (748374923)») в заголовке фрейма данных не изменяется на «Учетная запись хранения», поскольку оба термина не найдены в списке столбцов. Некоторые другие значения просто отличаются между описанным ответом JSON и ожидаемой структурой.
Комментарии:
1. Спасибо за ваш вклад. Я создал функцию, которая возвращает ответ api и сохраняет его в ‘api_response’, а затем попробовал ‘data = json.loads (api_response)’. Однако я получаю сообщение «ошибка типа: объект JSON должен быть str, bytes или bytearray, а не dict». Есть идеи, почему?
2.
json.loads()
ожидает str, bytes или bytearray… Итак, я фактически передал ответ JSON в виде строки функции (используя тройные кавычки, поскольку он занимает несколько строк). Я разъяснил эту часть в ответе. Это сработало для тестирования. Тем не менее, в вашем конкретном контексте, вероятно, более подходящим является какое-то другое решение, в зависимости от того, как вы получаете json из вызова API.3. Спасибо роза — боюсь, я неправильно понял. Спасибо за отличное объяснение; Я многому научился!