Создание Pandas DataFrame из SmartSheet API (вложенный, неудобный, JSON) - PullRequest
0 голосов
/ 13 февраля 2020

Я пытаюсь подключиться к интерфейсу SmartSheet API моего офиса через Python, чтобы создать несколько панелей отслеживания производительности, которые используют данные вне SmartSheet. Все, что я хочу сделать, это создать простой DataFrame, в котором поля отражают columnId, а значения ячеек отражают ключ displayValue в словаре Smartsheet. Я делаю это с помощью стандартного API request.get, а не документации API SmartSheet, потому что с последним мне было труднее работать.

Таблица (образец) настроена как:

Number  Letter  Name
1       A       Joe
2       B       Jim
3       C       Jon

Синтаксис JSON из запроса GET листа:

{'id': 339338304219012,
 'name': 'Sample Smartsheet',
 'version': 1,
 'totalRowCount': 3,
 'accessLevel': 'OWNER',
 'effectiveAttachmentOptions': ['GOOGLE_DRIVE',
  'EVERNOTE',
  'DROPBOX',
  'ONEDRIVE',
  'LINK',
  'FILE',
  'BOX_COM',
  'EGNYTE'],
 'ganttEnabled': False,
 'dependenciesEnabled': False,
 'resourceManagementEnabled': False,
 'cellImageUploadEnabled': True,
 'userSettings': {'criticalPathEnabled': False, 'displaySummaryTasks': True},
 'userPermissions': {'summaryPermissions': 'ADMIN'},
 'hasSummaryFields': False,
 'permalink': 'https://app.smartsheet.com/sheets/5vxMCJQhMV7VFFPMVfJgg2hX79rj3fXgVGG8fp61',
 'createdAt': '2020-02-13T16:32:02Z',
 'modifiedAt': '2020-02-14T13:15:18Z',
 'isMultiPicklistEnabled': True,
 'columns': [{'id': 6273865019090820,
   'version': 0,
   'index': 0,
   'title': 'Number',
   'type': 'TEXT_NUMBER',
   'primary': True,
   'validation': False,
   'width': 150},
  {'id': 4022065205405572,
   'version': 0,
   'index': 1,
   'title': 'Letter',
   'type': 'TEXT_NUMBER',
   'validation': False,
   'width': 150},
  {'id': 8525664832776068,
   'version': 0,
   'index': 2,
   'title': 'Name',
   'type': 'TEXT_NUMBER',
   'validation': False,
   'width': 150}],
 'rows': [{'id': 8660990817003396,
   'rowNumber': 1,
   'expanded': True,
   'createdAt': '2020-02-14T13:15:18Z',
   'modifiedAt': '2020-02-14T13:15:18Z',
   'cells': [{'columnId': 6273865019090820, 'value': 1.0, 'displayValue': '1'},
    {'columnId': 4022065205405572, 'value': 'A', 'displayValue': 'A'},
    {'columnId': 8525664832776068, 'value': 'Joe', 'displayValue': 'Joe'}]},
  {'id': 498216492394372,
   'rowNumber': 2,
   'siblingId': 8660990817003396,
   'expanded': True,
   'createdAt': '2020-02-14T13:15:18Z',
   'modifiedAt': '2020-02-14T13:15:18Z',
   'cells': [{'columnId': 6273865019090820, 'value': 2.0, 'displayValue': '2'},
    {'columnId': 4022065205405572, 'value': 'B', 'displayValue': 'B'},
    {'columnId': 8525664832776068, 'value': 'Jim', 'displayValue': 'Jim'}]},
  {'id': 5001816119764868,
   'rowNumber': 3,
   'siblingId': 498216492394372,
   'expanded': True,
   'createdAt': '2020-02-14T13:15:18Z',
   'modifiedAt': '2020-02-14T13:15:18Z',
   'cells': [{'columnId': 6273865019090820, 'value': 3.0, 'displayValue': '3'},
    {'columnId': 4022065205405572, 'value': 'C', 'displayValue': 'C'},
    {'columnId': 8525664832776068, 'value': 'Jon', 'displayValue': 'Jon'}]}]}

Вот два способа решения проблемы:

ВХОД:

from pandas.io.json import json_normalize
samplej = sample.json()
s_rows = json_normalize(data=samplej['rows'], record_path='cells', meta=['id', 'rowNumber'])
s_rows

OUTPUT:

DataFrame с columnId, value, disdlayValue, id и rowNumber в качестве собственных полей.

Если бы я мог понять, как правильно перенести эти данные, я мог бы заставить их работать, но это кажется невероятно сложным.

INPUT:

samplej = sample.json()
cellist = []
def get_cells():
    srows = samplej['rows']
    for s_cells in srows:
        scells = s_cells['cells']
        cellist.append(scells)
get_cells()
celldf = pd.DataFrame(cellist)
celldf

OUTPUT:

Возвращает DataFrame с правильным количеством столбцов и строк, но каждая ячейка заполняется словарем, который выглядит как

In [14]:
celldf.loc[1,1]
Out [14]:
{'columnId': 4022065205405572, 'value': 'B', 'displayValue': 'B'}

Если был способ удалить все, кроме значение, соответствующее ключу displayValue в каждой ячейке, это, вероятно, решило бы мою проблему. Опять же, хотя это кажется странно сложным.

Я довольно новичок в Python и работаю с API, так что может быть простой способ решения проблемы, которую я пропускаю. Или, если у вас есть предложение приблизиться к возможным решениям, которые я изложил выше, я весь в ушах. Спасибо за вашу помощь!

1 Ответ

0 голосов
/ 14 февраля 2020

Вы должны использовать поле columns:

colnames = {x['id']: x['title'] for x in samplej['columns']}
columns = [x['title'] for x in samplej['columns']]
cellist = [{colnames[scells['columnId']]: scells['displayValue']
            for scells in s_cells['cells']} for s_cells in samplej['rows']]
celldf = pd.DataFrame(cellist, columns=columns)

Это дает ожидаемый результат:

  Number Letter Name
0      1      A  Joe
1      2      B  Jim
2      3      C  Jon

Если некоторые ячейки могут содержать только columnId, но нет Поле displayValue, scells['displayValue'] должно быть заменено в вышеприведенном коде на scells.get('displayValue', defaultValue), где defaultValue может быть None, np.nan или любым другим соответствующим значением по умолчанию.

...