1 Вопрос: Как извлечь данные из иерархии заказных продаж salesforce, используя Python & Pandas

вопрос создан в Wed, May 8, 2019 12:00 AM

Резюме

Короче говоря, мне нужно извлечь данные из серии панд, содержащих отдельные OrderedDicts. Пока прогресс был хорошим, но теперь я столкнулся с камнем преткновения.

Когда я определяю свой собственный фрейм данных для демонстрационных целей при переполнении стека, я могу использовать функцию индексации OrderedDict, чтобы найти нужные данные в OrderedDict. Однако, когда я работаю с реальными данными, в которых я не определяю OrderedDict внутри фрейма данных, мне приходится анализировать OrderedDict через стандартный пакет Json с помощью функции.

OrderedDicts, с которыми я работаю, имеют несколько вложенных иерархий, которые могут манипулировать обычным способом ...

from collections import OrderedDict

example = OrderedDict([('attributes', OrderedDict([('type', 'Name'), ('url', 'URLHERE')])), ('UserRole', OrderedDict([('attributes', OrderedDict([('type', 'UserRole'), ('url', 'URLHERE')])), ('Name', 'Telephone Sales')]))])

print(example['UserRole']['Name'])

Приведенный выше код приведет к 'Telephone Sales'. Однако это работает только тогда, когда я определил DataFrame вручную для примера, так как я должен использовать пакет collection.OrderedDict без необходимости разбора.

Фон сильный> р>

Ниже приведен код, который я подготовил для StackOverflow и который демонстрирует мою проблему.

import pandas as pd
import json
from collections import OrderedDict

# Settings
pd.set_option('display.max_colwidth', -1)


# Functions
def extract_odict_item(odict, key_1, key_2=None):
    data = json.dumps(odict)
    final_data = json.loads(data)

    if key_2 is None:
        if final_data is not None:
            return final_data[key_1]
        else:
            return None

    elif key_2 is not None:
        if final_data is not None:
            return final_data[key_1][key_2]
        else:
            return None

# Data
accounts = [
    OrderedDict([('attributes', OrderedDict([('type', 'Account'), ('url', 'URLHERE')])), ('Name', 'Supermarket'), ('AccountNumber', 'ACC1234'), ('MID__c', '123456789')]),
    OrderedDict([('attributes', OrderedDict([('type', 'Account'), ('url', 'URLHERE')])), ('Name', 'Bar'), ('AccountNumber', 'ACC9876'), ('MID__c', '987654321')]),
    OrderedDict([('attributes', OrderedDict([('type', 'Account'), ('url', 'URLHERE')])), ('Name', 'Florist'), ('AccountNumber', 'ACC1298'), ('MID__c', '123459876')])
]

owner = [
    OrderedDict([('attributes', OrderedDict([('type', 'Name'), ('url', 'URLHERE')])), ('UserRole', OrderedDict([('attributes', OrderedDict([('type', 'UserRole'), ('url', 'URLHERE')])), ('Name', 'Telephoone Sales')]))]),
    OrderedDict([('attributes', OrderedDict([('type', 'Name'), ('url', 'URLHERE')])), ('UserRole', OrderedDict([('attributes', OrderedDict([('type', 'UserRole'), ('url', 'URLHERE')])), ('Name', 'Field Sales')]))]),
    OrderedDict([('attributes', OrderedDict([('type', 'Name'), ('url', 'URLHERE')])), ('UserRole', OrderedDict([('attributes', OrderedDict([('type', 'UserRole'), ('url', 'URLHERE')])), ('Name', 'Online Sale')]))])
]

# Dataframe
df = pd.DataFrame({'ConvertedAccounts': accounts,
                   'Owner': owner
                   })

# Extract data from OrderedDict using usual indexing
df['MerchantID'] = df['ConvertedAccounts'].apply(lambda x: x['MID__c'])
df['UserRole'] = df['Owner'].apply(lambda x: x['UserRole']['Name'])

# Extract data from OrderedDict using function
df['extracted_MerchantID'] = df['ConvertedAccounts'].apply(lambda x: extract_odict_item(x, 'MID__c'))
df['extracted_UserRole'] = df['Owner'].apply(
    lambda x: extract_odict_item(x, 'UserRole', 'Name'))

# Drop junk columns
df = df.drop(columns=['ConvertedAccounts', 'Owner'])

print(df)

В приведенном выше коде у меня есть функция extract_odict_item (), которую я могу использовать для извлечения данных из каждого отдельного OrderedDict в рамках фрейма данных и помещения их в новый столбец, пока я указываю, что я хочу. Тем не менее, я хочу иметь возможность указать столько аргументов, сколько я хочу, с помощью * args, чтобы представить, сколько гнезд я хочу обойти и извлечь значение из окончательного ключа.

Ожидаемые результаты

Я хочу использовать приведенную ниже функцию для приема нескольких аргументов и создания вложенного селектора индекса, например, так ...

# Functions
def extract_odict_item(odict, *args):
    data = json.dumps(odict)
    final_data = json.loads(data)
    if len(args) == 0:
        raise Exception('Requires atleast 1 argument')

    elif len(args) == 1:
        if final_data is not None:
            return final_data[args[0]]
        else:
            return None

    elif len(args) > 1:
        ### Pseudo Code ###
        # if final_data is not None:
        #     return final_data[args[0]][args[1]][args[2]] etc.....
        # else:
        #     return None

Так что, если я позвоню extract_odict_item

extract_odict_item(odict, 'item1', 'item2', 'item3') р>

Должен вернуть final_data['item1']['item2']['item3']

Я, возможно, слишком усложнил это, но я не могу думать ни о чем другом, и даже если это возможно в Python.

Ответ

Мне удалось использовать рекурсивную функцию для обработки выбора нужных мне данных из заказа.

import json
from collections import OrderedDict

# Settings
pd.set_option('display.max_colwidth', -10)

# Data
owner = [
    OrderedDict([('attributes', OrderedDict([('type', 'Name'), ('url', 'URLHERE')])), ('UserRole', OrderedDict([('attributes', OrderedDict([('type', 'UserRole'), ('url', 'URLHERE')])), ('Name', 'Telephoone Sales')]))]),
    OrderedDict([('attributes', OrderedDict([('type', 'Name'), ('url', 'URLHERE')])), ('UserRole', OrderedDict([('attributes', OrderedDict([('type', 'UserRole'), ('url', 'URLHERE')])), ('Name', 'Field Sales')]))]),
    OrderedDict([('attributes', OrderedDict([('type', 'Name'), ('url', 'URLHERE')])), ('UserRole', OrderedDict([('attributes', OrderedDict([('type', 'UserRole'), ('url', 'URLHERE')])), ('Name', 'Online Sale')]))])
]

# Functions
def rec_ext(odict, item_list):
    new_list = item_list.copy()
    data = json.dumps(odict)
    final_data = json.loads(data)
    el = new_list.pop()
    if isinstance(final_data[el], dict):
        return rec_ext(final_data[el], new_list)
    else:
        return final_data[el]


# Dataframe
df = pd.DataFrame({'owner': owner
                   })

my_columns = ['UserRole', 'Name']
my_columns.reverse()
df['owner2'] = df['owner'].apply(lambda x: rec_ext(x, my_columns))

print(df['owner2'])
    
1
1 ответ                              1                         

Это не точный ответ, но вы можете попробовать рекурсию, если я правильно понимаю ваш вопрос -

d = {1: {2: {3: {4: 5}}}}#Arbitrarily nested dict
l = [1, 2, 3, 4]

def rec_ext(my_dict, my_list):
     el = my_list.pop()
     if isinstance(my_dict[el], dict):
         return rec_ext(my_dict[el], my_list)
     else:
         return my_dict[el]

l.reverse() #we reverse because we are "popping" in the function 
rec_ext(d, l)
#Returns 5
    
1
2019-05-08 16: 39: 42Z
  1. Спасибо, Мортц, ваш ответ очень помог! Проблема в том, что я получаю IndexError: pop из пустого списка, когда применяю функцию к столбцу Pandas с помощью лямбды my_columns = ['UserRole', 'Name'] my_columns.reverse() df['owner2'] = df['owner'].apply(lambda x: rec_ext(x, my_columns))
    2019-05-09 09: 38: 20Z
  2. def rec_ext2(odict, column_list): new_list = column_list data = json.dumps(odict) final_data = json.loads(data) try: el = new_list.pop() if isinstance(final_data[el], dict): return rec_ext(final_data[el], new_list) else: return final_data[el] except IndexError: pass Демонстрирует проблему так же, как работает функция для 1 элемента в серии панд
    2019-05-09 09: 43: 00Z
  3. Причина в том, что список my_columns изменен на месте - это означает, что он работает для первой строки вашего фрейма данных, а для последующих строк, список my_columns - []. Вы можете обойти эту проблему, вызвав свою функцию df['owner2'] = df['owner'].apply(lambda x: rec_ext(x, my_columns[:])). my_columns[:] гарантирует, что вы передаете копию списка вызову функции, а не ссылку на список. Надеюсь, это поможет
    2019-05-09 10: 45: 27Z
  4. Ух ты, я не знал этого, большое спасибо! Это заставляет меня задуматься, если .reverse () & [:] шаги могут быть встроены в саму функцию?
    2019-05-09 10: 50: 46Z
  5. Похоже, вы можете включить в функцию list.copy (), чтобы избежать необходимости использовать список [:] slice new_list = item_list.copy()
    2019-05-09 10: 55: 52Z
источник размещен Вот