Как отсортировать фрейм данных pandas по полю json

#python #pandas #columnsorting

#python #pandas #сортировка столбцов

Вопрос:

У меня есть такие данные в фрейме данных Pandas

    id     import_id              investor_id     loan_id      meta
   35736  unremit_loss_100312         Q05         0051765139  {u'total_paid': u'75', u'total_expense': u'75'}
   35737  unremit_loss_100313         Q06         0051765140  {u'total_paid': u'77', u'total_expense': u'78'}
   35739  unremit_loss_100314         Q06         0051765141  {u'total_paid': u'80', u'total_expense': u'65'}
  

Как сортировать на основе total_expense, который является значением поля json
, например: total_expense в мета-поле

Вывод должен быть

 id     import_id              investor_id     loan_id      meta
35739  unremit_loss_100314         Q06         0051765141  {u'total_paid': u'80', u'total_expense': u'65'}
35736  unremit_loss_100312         Q05         0051765139  {u'total_paid': u'75', u'total_expense': u'75'}
35737  unremit_loss_100313         Q06         0051765140  {u'total_paid': u'77', u'total_expense': u'78'}
  

Комментарии:

1. Попробуйте df = df.iloc[df['meta'].str.get('total_expense').argsort().values, :] и посмотрите, работает ли это. Если нет, вам нужно будет показать нам более 1 строки данных.

2. это не работает, все строки становятся одинаковыми значениями, если я использую вашу логику. вопрос изменен и добавлен дополнительные строки @coldspeed

3. Я предоставил два варианта. Кажется, вам нужно преобразовать извлеченные значения в целое число перед сортировкой. Посмотрите и, пожалуйста, четко сообщите мне, в чем проблема, если это не сработало. Спасибо.

4. @JameelGrand Я попытался найти простой способ добиться этого, решает ли это вашу проблему?

Ответ №1:

Настройка и предварительная обработка

 import ast
import numpy as np

if isinstance(x.at[0, 'meta'], str):
    df['meta'] = df['meta'].map(ast.literal_eval)
  

str.get с Series.argsort

 df.iloc[df['meta'].str.get('total_expense').astype(int).argsort()]

      id            import_id investor_id   loan_id                                         meta
2  35739  unremit_loss_100314         Q06  51765141  {'total_paid': '80', 'total_expense': '65'}
0  35736  unremit_loss_100312         Q05  51765139  {'total_paid': '75', 'total_expense': '75'}
1  35737  unremit_loss_100313         Q06  51765140  {'total_paid': '77', 'total_expense': '78'}
  

Понимание списка

 df.iloc[np.argsort([int(x.get('total_expense', '-1')) for x in df['meta']])]

      id            import_id investor_id   loan_id                                         meta
2  35739  unremit_loss_100314         Q06  51765141  {'total_paid': '80', 'total_expense': '65'}
0  35736  unremit_loss_100312         Q05  51765139  {'total_paid': '75', 'total_expense': '75'}
1  35737  unremit_loss_100313         Q06  51765140  {'total_paid': '77', 'total_expense': '78'}
  

Если вам нужно обработать NAN / отсутствующие данные, используйте

 u = [  
  int(x.get('total_expense', '-1')) if isinstance(x, dict) else -1 
  for x in df['meta']
]
df.iloc[np.argsort(u)]

      id            import_id investor_id   loan_id                                         meta
2  35739  unremit_loss_100314         Q06  51765141  {'total_paid': '80', 'total_expense': '65'}
0  35736  unremit_loss_100312         Q05  51765139  {'total_paid': '75', 'total_expense': '75'}
1  35737  unremit_loss_100313         Q06  51765140  {'total_paid': '77', 'total_expense': '78'}
  

Ответ №2:

Использовать:

 print (df)
      id            import_id investor_id   loan_id  
0  35736  unremit_loss_100312         Q05  51765139   
1  35736  unremit_loss_100312         Q05  51765139   
2  35736  unremit_loss_100312         Q05  51765139   

                                               meta  
0   {u'total_paid': u'75', u'total_expense': u'75'}  
1   {u'total_paid': u'75', u'total_expense': u'20'}  
2  {u'total_paid': u'75', u'total_expense': u'100'}  

import ast

df['meta'] = df['meta'].apply(ast.literal_eval)

df = df.iloc[df['meta'].str['total_expense'].astype(int).argsort()]

print (df)
      id            import_id investor_id   loan_id  
1  35736  unremit_loss_100312         Q05  51765139   
0  35736  unremit_loss_100312         Q05  51765139   
2  35736  unremit_loss_100312         Q05  51765139   

                                           meta  
1   {'total_paid': '75', 'total_expense': '20'}  
0   {'total_paid': '75', 'total_expense': '75'}  
2  {'total_paid': '75', 'total_expense': '100'} 
  

Если возможно, если отсутствует total_expense ключ для какой-либо строки, преобразуйте пропущенные значения в некоторое целое число ниже, как и все другие значения, например -1 , для первой позиции этих строк:

 print (df)
      id            import_id investor_id   loan_id  
0  35736  unremit_loss_100312         Q05  51765139   
1  35736  unremit_loss_100312         Q05  51765139   
2  35736  unremit_loss_100312         Q05  51765139   

                                              meta  
0  {u'total_paid': u'75', u'total_expense': u'75'}  
1  {u'total_paid': u'75', u'total_expense': u'20'}  
2                           {u'total_paid': u'75'} 

df['meta'] = df['meta'].apply(ast.literal_eval)


df = df.iloc[df['meta'].str.get('total_expense').fillna(-1).astype(int).argsort()]
print (df)
      id            import_id investor_id   loan_id  
2  35736  unremit_loss_100312         Q05  51765139   
1  35736  unremit_loss_100312         Q05  51765139   
0  35736  unremit_loss_100312         Q05  51765139   

                                          meta  
2                         {'total_paid': '75'}  
1  {'total_paid': '75', 'total_expense': '20'}  
0  {'total_paid': '75', 'total_expense': '75'}  
  

Другое решение:

 df['new'] = df['meta'].str.get('total_expense').astype(int)
df = df.sort_values('new').drop('new', axis=1)
  

Комментарии:

1. Спасибо за ответ, это приводит все мои строки к одному и тому же значению. ie. все значения моего поля id становятся одинаковыми, обновленный вопрос с большим количеством строк.

2. @JameelGrand — Можете ли вы добавить вывод после моего решения вопроса?

3. @JameelGrand — добавлено новое решение, вы можете его проверить?

4. не повезло: (он выдает несортированный фрейм данных, а df[‘meta’].str.get(‘total_expense’) выдает все значения Nan, фактически не принимая значения

5. @JameelGrand — вы используете df['meta'] = df['meta'].apply(ast.literal_eval) как первый шаг? а потом df['new'] = df['meta'].str.get('total_expense').astype(int) ?

Ответ №3:

РЕДАКТИРОВАТЬ 2:

Найден немного лучший способ сделать это без использования apply:

 from pandas.io.json import json_normalize

df = pd.concat([df, json_normalize(df['meta'])], axis = 1)
       .sort_values(by = 'total_expense')
       .drop(columns = ['total_paid', 'total_expense'])
  

Редактировать:

 df = pd.concat([df, df['meta'].apply(pd.Series)], axis = 1).sort_values(by = 'total_expense').drop(columns = ['total_paid', 'total_expense'])
  

Если вы хотите, чтобы он выглядел как оригинал, просто удалите столбцы, которые вы объединяете после сортировки.

Оригинал:

 df = pd.concat([df, df['meta'].apply(pd.Series)], axis = 1).drop(columns ='meta').sort_values(by = 'total_expense')
  

df['meta'].apply(pd.Series) превращает dicts в столбце meta в собственный df. Мы можем объединить его с оригиналом, удалить столбец meta (как избыточный), а затем отсортировать значения по «общему расходу»

Комментарии:

1. @JameelGrand — отредактировал мой комментарий, чтобы он выглядел как исходный df

Ответ №4:

Использование регулярных выражений:

 df = pd.read_clipboard(r'ss ')
pattern = r"""u'total_expense': u'([0-9.] )'"""
df['total_expense'] = df.meta.str.extract(pattern)
df.sort_values('total_expense')
  

Использование apply:

 df['total_expense'] = df.meta.apply(eval).apply(
                        lambda x: x.get('total_expense', -1))
df.sort_values('total_expense')
  

Вывод:

       id            import_id investor_id   loan_id  
2  35739  unremit_loss_100314         Q06  51765141   
0  35736  unremit_loss_100312         Q05  51765139   
1  35737  unremit_loss_100313         Q06  51765140   

                                              meta total_expense  
2  {u'total_paid': u'80', u'total_expense': u'65'}            65  
0  {u'total_paid': u'75', u'total_expense': u'75'}            75  
1  {u'total_paid': u'77', u'total_expense': u'78'}            78