Как отфильтровать список на основе возрастающих значений?

#python #list #if-statement #floating-point

#python #pandas #Список #if-оператор #с плавающей запятой

Вопрос:

У меня есть следующие 3 списка:

 minimal_values = ['0,32', '0,35', '0,45']
maximal_values = ['0,78', '0,85', '0,72']

my_list = [
    ['Morocco', 'Meat', '190,00', '0,15'], 
    ['Morocco', 'Meat', '189,90', '0,32'], 
    ['Morocco', 'Meat', '189,38', '0,44'],
    ['Morocco', 'Meat', '188,94', '0,60'],
    ['Morocco', 'Meat', '188,49', '0,78'],
    ['Morocco', 'Meat', '187,99', '0,70'],
    ['Spain', 'Meat', '190,76', '0,10'], 
    ['Spain', 'Meat', '190,16', '0,20'], 
    ['Spain', 'Meat', '189,56', '0,35'],
    ['Spain', 'Meat', '189,01', '0,40'],
    ['Spain', 'Meat', '188,13', '0,75'],
    ['Spain', 'Meat', '187,95', '0,85'],
    ['Italy', 'Meat', '190,20', '0,11'],
    ['Italy', 'Meat', '190,10', '0,31'], 
    ['Italy', 'Meat', '189,32', '0,45'],
    ['Italy', 'Meat', '188,61', '0,67'],
    ['Italy', 'Meat', '188,01', '0,72'],
    ['Italy', 'Meat', '187,36', '0,55']]
 

Я пытаюсь фильтровать my_list на основе, если index [-1] находится между значением in minimal_values и значением in maximal_values .Эти значения соответствуют минимальным и максимальным значениям по странам. Я также выполняю вычитание внутри списка. Итак, для Марокко мне нужны только строки, где index[-1] находится между 0,32 и т.д. 0,78 Проблема в том, что после 0,78 того, как значения уменьшатся 0,70 , это означает, что строка также удовлетворяет оператору if.

Примечание: значения в my_list -1 сначала возрастают, а затем убывают. Мне нужны только строки в возрастающей части, а не в нисходящей части. Я не уверен, как решить эту проблему.

Это мой код:

 price = 500

# Convert values to float.
minimal_values = [float(i.replace(',', '.')) for i in minimal_values]
maximal_values = [float(i.replace(',', '.')) for i in maximal_values]

# Collect all unique countries in a list.
countries = list(set(country[0] for country in my_list))

results = []
for l in my_list:
    i = countries.index(l[0])
    if minimal_values[i] <= float(l[-1].replace(',', '.')) <= maximal_values[i]:
        new_index_2 = price - float(l[-2].replace(',', '.'))
        l[-2] = new_index_2
        results.append(l)

print(results)
 

Это мой текущий результат:

 [['Morocco', 'Meat', '189.90', '0,32'], 
['Morocco', 'Meat', 310.62, '0,44'], 
['Morocco', 'Meat', 311.06, '0,60'], 
['Morocco', 'Meat', 311.51, '0,78'], 
['Morocco', 'Meat', 312.01, '0,70'], 
['Spain', 'Meat', 310.44, '0,35'], 
['Spain', 'Meat', 310.99, '0,40'], 
['Spain', 'Meat', 311.87, '0,75'], 
['Spain', 'Meat', '312.05', '0,85'],
['Italy', 'Meat', 310.68, '0,45'], 
['Italy', 'Meat', 311.39, '0,67'], 
['Italy', 'Meat', 311.99, '0,72'], 
['Italy', 'Meat', 312.64, '0,55']]
 

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

  [['Morocco', 'Meat', '189.90', '0,32'], 
    ['Morocco', 'Meat', 310.62, '0,44'], 
    ['Morocco', 'Meat', 311.06, '0,60'], 
    ['Morocco', 'Meat', 311.51, '0,78'], 
    ['Spain', 'Meat', 310.44, '0,35'], 
    ['Spain', 'Meat', 310.99, '0,40'], 
    ['Spain', 'Meat', 311.87, '0,75'],
    ['Spain', 'Meat', '312.05', '0,85'], 
    ['Italy', 'Meat', 310.68, '0,45'], 
    ['Italy', 'Meat', 311.39, '0,67'], 
    ['Italy', 'Meat', 311.99, '0,72']]
 

***** Ответы, связанные с Pandas, также приветствуются.

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

1. Это кажется тихим интересным случаем. Мне любопытно узнать, как это можно решить.

2. Да, я тоже.. тихий сложный, хотя я думаю.

3. Рассматривали ли вы возможность использования pandas для этой задачи (и, вероятно, многих других следующих задач, подобных этой …), Для этого типа задач есть встроенные, гораздо более интуитивно понятные и простые операции…

4. Я не уверен, как использовать padnas специально для этого.

5. добавьте к вопросу, что вы принимаете решения, связанные с pandas.

Ответ №1:

решение pandas:

 import pandas as pd
import numpy as np

# create input dataframe
my_list = [
    ['Morocco', 'Meat', '190,00', '0,15'], 
    ['Morocco', 'Meat', '189,90', '0,32'], 
    ['Morocco', 'Meat', '189,38', '0,44'],
    ['Morocco', 'Meat', '188,94', '0,60'],
    ['Morocco', 'Meat', '188,49', '0,78'],
    ['Morocco', 'Meat', '187,99', '0,70'],
    ['Spain', 'Meat', '190,76', '0,10'], 
    ['Spain', 'Meat', '190,16', '0,20'], 
    ['Spain', 'Meat', '189,56', '0,35'],
    ['Spain', 'Meat', '189,01', '0,40'],
    ['Spain', 'Meat', '188,13', '0,75'],
    ['Spain', 'Meat', '187,95', '0,85'],
    ['Italy', 'Meat', '190,20', '0,11'],
    ['Italy', 'Meat', '190,10', '0,31'], 
    ['Italy', 'Meat', '189,32', '0,45'],
    ['Italy', 'Meat', '188,61', '0,67'],
    ['Italy', 'Meat', '188,01', '0,72'],
    ['Italy', 'Meat', '187,36', '0,55']]

dfi = pd.DataFrame(my_list).applymap(lambda x: x.replace(',', '.'))
dfi[[2, 3]] = dfi[[2, 3]].astype(float)
print(dfi)

#         0     1       2     3
# 0   Morocco  Meat  190.00  0.15
# 1   Morocco  Meat  189.90  0.32
# 2   Morocco  Meat  189.38  0.44
# 3   Morocco  Meat  188.94  0.60
# 4   Morocco  Meat  188.49  0.78
# 5   Morocco  Meat  187.99  0.70
# 6     Spain  Meat  190.76  0.10
# 7     Spain  Meat  190.16  0.20
# 8     Spain  Meat  189.56  0.35
# 9     Spain  Meat  189.01  0.40
# 10    Spain  Meat  188.13  0.75
# 11    Spain  Meat  187.95  0.85
# 12    Italy  Meat  190.20  0.11
# 13    Italy  Meat  190.10  0.31
# 14    Italy  Meat  189.32  0.45
# 15    Italy  Meat  188.61  0.67
# 16    Italy  Meat  188.01  0.72
# 17    Italy  Meat  187.36  0.55

# create df_filter with contry and min_v, max_v
minimal_values = ['0,32', '0,35', '0,45']
maximal_values = ['0,78', '0,85', '0,72']
minimal_values = [float(i.replace(',', '.')) for i in minimal_values]
maximal_values = [float(i.replace(',', '.')) for i in maximal_values]

df_filter = pd.DataFrame(list(zip(dfi[0].unique().tolist(),
                                  minimal_values,
                                  maximal_values)))
df_filter.columns = [0, 'min_v', 'max_v']
print(df_filter)
#          0  min_v  max_v
# 0  Morocco   0.32   0.78
# 1    Spain   0.35   0.85
# 2    Italy   0.45   0.72

# merge dfi and fi_filter
dfm = pd.merge(dfi, df_filter, on=0, how='left')
print(dfm)

#          0     1       2     3  min_v  max_v
# 0   Morocco  Meat  190.00  0.15   0.32   0.78
# 1   Morocco  Meat  189.90  0.32   0.32   0.78
# 2   Morocco  Meat  189.38  0.44   0.32   0.78
# 3   Morocco  Meat  188.94  0.60   0.32   0.78
# 4   Morocco  Meat  188.49  0.78   0.32   0.78
# 5   Morocco  Meat  187.99  0.70   0.32   0.78
# 6     Spain  Meat  190.76  0.10   0.35   0.85
# 7     Spain  Meat  190.16  0.20   0.35   0.85
# 8     Spain  Meat  189.56  0.35   0.35   0.85
# 9     Spain  Meat  189.01  0.40   0.35   0.85
# 10    Spain  Meat  188.13  0.75   0.35   0.85
# 11    Spain  Meat  187.95  0.85   0.35   0.85
# 12    Italy  Meat  190.20  0.11   0.45   0.72
# 13    Italy  Meat  190.10  0.31   0.45   0.72
# 14    Italy  Meat  189.32  0.45   0.45   0.72
# 15    Italy  Meat  188.61  0.67   0.45   0.72
# 16    Italy  Meat  188.01  0.72   0.45   0.72
# 17    Italy  Meat  187.36  0.55   0.45   0.72

# filter min_v <= column 3 <= max_v
cond = dfm[3].ge(dfm.min_v) amp; dfm[3].le(dfm.max_v)
dfm = dfm[cond].copy()

# filter 3 that is not ascending
cond = dfm.groupby(0)[3].diff() < 0
dfo = dfm.loc[~cond, [0,1,2,3]].reset_index(drop=True)

# outut result
price = 500
dfo[2] = price - dfo[2]

print(dfo)

#           0     1       2     3
# 0   Morocco  Meat  310.10  0.32
# 1   Morocco  Meat  310.62  0.44
# 2   Morocco  Meat  311.06  0.60
# 3   Morocco  Meat  311.51  0.78
# 4     Spain  Meat  310.44  0.35
# 5     Spain  Meat  310.99  0.40
# 6     Spain  Meat  311.87  0.75
# 7     Spain  Meat  312.05  0.85
# 8     Italy  Meat  310.68  0.45
# 9     Italy  Meat  311.39  0.67
# 10    Italy  Meat  311.99  0.72
 

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

1. Этот код выполняется примерно в 1000 раз медленнее , чем простой цикл в моем ответе. ideone.com/EG4SNd

2. dfi['num2'] = dfi['num2'].str.replace('0,','').astype(int); dfi['num1'] = dfi['num1'].str.replace(r',','.').astype(float) вместо функции apply … ускорит ли это,

3. Вы правы! Это медленнее, но проще. Это просто альтернативный способ.

4. @adirabargil нет, импорт выполняется в той setup части кода, которая выполняется только один раз и не учитывается во времени. Из руководства : «Время выполнения setup исключается из общего времени выполнения»

Ответ №2:

Обратите внимание, что у вас есть проблема в вашем коде в том, что порядок элементов countries не обязательно совпадает с порядком стран в my_list . Проще просто обрабатывать страны по мере обработки списка, делая пометку при изменении названия страны. Затем вы можете добавить флаг в свой цикл, который указывает, что обработка для этой страны завершена (когда текущее значение меньше предыдущего значения), и если это так, игнорируйте оставшиеся значения для этой страны:

 # Convert values to float.
minimal_values = [float(i.replace(',', '.')) for i in minimal_values]
maximal_values = [float(i.replace(',', '.')) for i in maximal_values]

# Collect all unique countries in a list.
results = []
finished_country = -1
country_index = -1
last_country = ''
for l in my_list:
    country = l[0]
    if country != last_country:
        country_index  = 1
    last_country = country
    value = float(l[-1].replace(',', '.'))
    if finished_country == country_index or value < minimal_values[country_index]:
        last_value = 0
        continue
    if value < last_value:
        finished_country = country_index
    elif value <= maximal_values[country_index]:
        new_index_2 = price - float(l[-2].replace(',', '.'))
        l[-2] = new_index_2
        results.append(l)
    last_value = value
 

Вывод для ваших выборочных данных:

 [
 ['Morocco', 'Meat', 310.1, '0,32'],
 ['Morocco', 'Meat', 310.62, '0,44'],
 ['Morocco', 'Meat', 311.06, '0,60'],
 ['Morocco', 'Meat', 311.51, '0,78'],
 ['Spain', 'Meat', 310.44, '0,35'],
 ['Spain', 'Meat', 310.99, '0,40'],
 ['Spain', 'Meat', 311.87, '0,75'],
 ['Spain', 'Meat', 312.05, '0,85'],
 ['Italy', 'Meat', 310.68, '0,45'],
 ['Italy', 'Meat', 311.39, '0,67'],
 ['Italy', 'Meat', 311.99, '0,72']
]
 

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

1. TypeError: '<' not supported between instances of 'float' and 'str'

2. @TangerCity вы удалили строки, преобразующие эти списки в float?

3. Ах, вот оно что. Позвольте мне проверить это.

Ответ №3:

 
minimal_values = [float(i.replace(',', '.')) for i in minimal_values]
maximal_values = [float(i.replace(',', '.')) for i in maximal_values]

countries_largest = {}
filtered_list = []
for row in my_list:
    country_name = row[0]
    value = float(row[-1].replace(',','.'))
    if country_name in countries_largest and value < countries_largest[country_name]:
        continue
    countries_largest[country_name] = value
    if not (minimal_values[len(countries_largest)-1] <= value <= maximal_values[len(countries_largest)-1]):
        continue
    filtered_list.append(row)
 
 [['Morocco', 'Meat', '189,90', '0,32'],
 ['Morocco', 'Meat', '189,38', '0,44'],
 ['Morocco', 'Meat', '188,94', '0,60'],
 ['Morocco', 'Meat', '188,49', '0,78'],
 ['Spain', 'Meat', '189,56', '0,35'],
 ['Spain', 'Meat', '189,01', '0,40'],
 ['Spain', 'Meat', '188,13', '0,75'],
 ['Spain', 'Meat', '187,95', '0,85'],
 ['Italy', 'Meat', '189,32', '0,45'],
 ['Italy', 'Meat', '188,61', '0,67'],
 ['Italy', 'Meat', '188,01', '0,72']]


 

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

1. Ваш вывод не совпадает с моим. Я не хочу, чтобы были включены значения по убыванию.

2. О, я не совсем понял это. Я отредактирую его.

Ответ №4:

Учитывая:

 minimal_values = ['0,32', '0,35', '0,45']
maximal_values = ['0,78', '0,85', '0,72']

my_list = [
    ['Morocco', 'Meat', '190,00', '0,15'], 
    ['Morocco', 'Meat', '189,90', '0,32'], 
    ['Morocco', 'Meat', '189,38', '0,44'],
    ['Morocco', 'Meat', '188,94', '0,60'],
    ['Morocco', 'Meat', '188,49', '0,78'],
    ['Morocco', 'Meat', '187,99', '0,70'],
    ['Spain', 'Meat', '190,76', '0,10'], 
    ['Spain', 'Meat', '190,16', '0,20'], 
    ['Spain', 'Meat', '189,56', '0,35'],
    ['Spain', 'Meat', '189,01', '0,40'],
    ['Spain', 'Meat', '188,13', '0,75'],
    ['Spain', 'Meat', '187,95', '0,85'],
    ['Italy', 'Meat', '190,20', '0,11'],
    ['Italy', 'Meat', '190,10', '0,31'], 
    ['Italy', 'Meat', '189,32', '0,45'],
    ['Italy', 'Meat', '188,61', '0,67'],
    ['Italy', 'Meat', '188,01', '0,72'],
    ['Italy', 'Meat', '187,36', '0,55']]
 

Во-первых, поскольку мы собираемся использовать его часто, давайте напишем небольшую процедуру преобразования, которая стандартизирует то, что мы подразумеваем под «плавающей точкой» в вашем случае:

 def conv(s):
    try:
        return float(s.replace(',','.'))
    except ValueError:
        return s
 

Теперь кажется, что ваши два списка строк minimal_values и maximal_values являются сопоставлением с минимальными и максимальными значениями по странам. Если это так, ваше использование countries = list(set(country[0] for country in my_list)) не будет работать, поскольку наборы расположены в произвольном порядке во всех версиях Python.

Если у вас есть Python 3.6 , вы можете сделать:

 countries = list({}.fromkeys(country[0] for country in my_list))
 

поскольку dicts сохраняют порядок вставки в Python 3.6 . Предполагая, что вы хотите что-то, что работает на всех версиях Python, вы можете вместо этого сделать:

 def uniqs_in_order(li):
    seen=set()
    return [e for e in li if not (e in seen or seen.add(e))]
    # Python 3.6 : return list({}.fromkeys(li))
 

Теперь вы можете создать отображение страны: кортеж минимального / максимального значения для этой страны:

 mapping={k:(min_, max_) for k,min_,max_ in 
    zip(uniqs_in_order([sl[0] for sl in my_list]), 
                        [conv(s) for s in minimal_values], 
                        [conv(s) for s in maximal_values])}

>>> mapping
{'Morocco': (0.32, 0.78), 'Spain': (0.35, 0.85), 'Italy': (0.45, 0.72)}
 

Теперь, наконец, мы можем фильтровать. Поскольку вы хотите принимать только значения, которые:

  1. Находятся в пределах минимального и максимального значений по странам и;
  2. Остановка, когда значения по странам больше не возрастают.

Мы можем использовать groupby from itertools, чтобы нарезать список списков по странам и выполнить эти два теста:

 from itertools import groupby

filt=[]
price = 500
for k,v in groupby(my_list, key=lambda sl: sl[0]):
    section=list(v)
    for i, row in enumerate(section):
        if i and conv(row[-1])<conv(section[i-1][-1]):
            break
        if mapping[row[0]][0]<=conv(row[-1])<=mapping[row[0]][1]:
            row[-2]=price-conv(row[-2])
            filt.append(row)        

>>> filt
[['Morocco', 'Meat', 310.1, '0,32'],
['Morocco', 'Meat', 310.62, '0,44'],
['Morocco', 'Meat', 311.06, '0,60'],
['Morocco', 'Meat', 311.51, '0,78'],
['Spain', 'Meat', 310.44, '0,35'],
['Spain', 'Meat', 310.99, '0,40'],
['Spain', 'Meat', 311.87, '0,75'],
['Spain', 'Meat', 312.05, '0,85'],
['Italy', 'Meat', 310.68, '0,45'],
['Italy', 'Meat', 311.39, '0,67'],
['Italy', 'Meat', 311.99, '0,72']]