Как проверить, имеет ли элемент в строке Pandas df значение со знаком равенства (значение после знака меняется) - PullRequest
1 голос
/ 26 февраля 2020

У меня есть фрейм данных с такими условиями:

  condition1 Condition2 Condition3 Condition4
0 duck>1          goat>2     sheep=0    chicken=0
1 duck=0          chicken=0  donkey>1   zebra>0

И список с одним условием для проверки по df:

list = ['goat','goat','duck','goat','duck']

Из этого списка, который я создал другой список, который показывает количество вхождений каждого элемента в list1

list2 = ['goat=3','duck=2']

. В этом примере я хочу получить обратно строку '0' в df, поскольку все условия выполнены (примечание: отсутствие овец и Курица в моем списке равна Овца = 0 и Курица = 0).

Количество категорий в условиях (в данном случае животных) так много, чтобы определить их заранее.

Как бы Я проверяю и возвращаю строки, в которых выполняются условия?

Спасибо

Ответы [ 3 ]

3 голосов
/ 26 февраля 2020

Есть и другие способы получить list2, которые лучше подходили бы для этой проблемы. Это решение основано исключительно на ваших переменных:

1. Простой метод (Bruteforce):

list2 = ['goat=3','duck=2']
dict2 = dict(item.split('=') for item in list2)
comparison_func = {'>': lambda x,y: x > y, 
                   '<': lambda x,y: x < y, 
                   '=': lambda x,y: x == y}

valid_rows = []
for row_ix, row_contents in df.iterrows():
    conditions_met = True
    for condition in row_contents:
        for operator in comparison_func:
            if operator in condition:
                animal, value = condition.split(operator)
                break

        is_true = comparison_func[operator]
        if not is_true(int(dict2.get(animal,0)), int(value)):
            conditions_met = False
            break
    if conditions_met:
        valid_rows.append(row_ix)
print(valid_rows)

Выход:

[0]

Позже вы можете сделать:

>>> df['conditions_met'] = df.index.isin(valid_rows)
>>> df
  condition1 Condition2 Condition3 Condition4  conditions_met
0     duck>1     goat>2    sheep=0  chicken=0            True
1     duck=0  chicken=0   donkey>1    zebra>0           False

2. Умный метод:

>>> list2 = ['goat=3','duck=2']
>>> dict2 = dict(item.split('=') for item in list2)
>>> globals().update(dict2)

>>> def is_true(x):
...     if '>' in x:
...         animal, value = x.split('>')
...         return int(globals().get(animal, 0)) > int(value)
...     elif '<' in x:
...         animal, value = x.split('<')
...         return int(globals().get(animal, 0)) < int(value)
...     else:
...         animal, value = x.split('=')
...         return int(globals().get(animal, 0)) == int(value)

>>> df.applymap(is_true)
   condition1  Condition2  Condition3  Condition4
0        True        True        True        True
1       False        True       False       False

>>> df[df.applymap(is_true).all(1)].index
Int64Index([0], dtype='int64')

>>> df['conditions_met'] = df.applymap(is_true).all(1)

>>> df
  condition1 Condition2 Condition3 Condition4  conditions_met
0     duck>1     goat>2    sheep=0  chicken=0            True
1     duck=0  chicken=0   donkey>1    zebra>0           False

ОБЪЯСНЕНИЕ для метода 1 :

# make a dict out of list2, there are other ways  
# to get to the next step directly from list1
dict2 = dict(item.split('=') for item in list2)

# make a mapping of checker functions wrt operators
comparison_func = {'>': lambda x,y: x > y, 
                   '<': lambda x,y: x < y, 
                   '=': lambda x,y: x == y}

# create an empty list to keep provision for the case
# when conditions are matched in multiple rows
valid_rows = []

# Iterate over the dataframe
for row_ix, row_contents in df.iterrows():

    # set a flag for each row
    conditions_met = True

    # iterate through each column in the row
    for condition in row_contents:

        # check which operator is in the current column
        for operator in comparison_func:

            # if operator found, split by it, store the (animal, value) and break
            if operator in condition:
                animal, value = condition.split(operator)
                break
        # get the comparison function for the operator, name the function is_true
        is_true = comparison_func[operator]

        # check if the function evaluates to true with given values
        # dict2.get(animal,0) will give the value for that animal from dict2
        # if the animal is not in dict2 it will return default value 0
        if not is_true(int(dict2.get(animal,0)), int(value)):
            # if condition did not meet, set conditions_met False, break
            conditions_met = False
            break

    # outside the loop, if the conditions_met stays True throughout the previous loop
    # append the row index to the valid_rows
    if conditions_met:
        valid_rows.append(row_ix)

print(valid_rows)

Объяснение для метода 2 :

globals() - это встроенная функция python, которая предоставляет доступ к глобальным переменным в пространстве имен через словарь. Здесь я обновляю пространство имен global с помощью dict2, т.е. теперь и goat, и duck существуют как глобальные переменные, и к ним можно обращаться из других функций для сравнения.

df.applymap(func) применяет функцию к каждому элементу информационного кадра.

df.all(axis) проверяет, все ли значения оцениваются как True для данного axis.

И это все.

1 голос
/ 26 февраля 2020

используйте collection.Counter, чтобы получить количество элементов в списке, затем примените функцию, которая проверяет ваше состояние для каждой строки, используя pandas .DataFrame.apply

Кадр данных и список должны быть переданы для применения

>>df
  condition1.   condition2  condition3  condition4
0     duck>1        goat>2     sheep=0   chicken=0
1     duck=0     chicken=0    donkey>1     zebra>0
2     duck=4     chicken=0    donkey>1     zebra>0

>>lst
['goat','goat','duck','goat','duck']

from collections import Counter
from collections import Counter
def bar(op,ctr,item):
    item = item.split(op)
    val = ctr.get(item[0])
    if item:
        if val:
            if op == '=':
                if int(val) < int(item[1]):
                    return False
            elif op == '>':
                if int(val) <= int(item[1]):
                    return False
        else:
            if int(item[1]) !=0:
                return False
    return True

def foo(row,lst):
    ctr = dict(Counter(lst))
    for item in list(row):
        if '=' in item:
            if not bar('=',ctr,item):
                return False  

        elif '>' in item:
            if not bar('>',ctr,item):
                return False   
    return True
df1['select'] = df.apply(lambda x: foo(x,lst),axis=1)
>>> print(df1[df1.select])
  condition1 condition2 condition3 condition4   select
0     duck>1     goat>2    sheep=0  chicken=0     True

РЕДАКТИРОВАТЬ : исправленный код

1 голос
/ 26 февраля 2020

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

dict1 = {'goat': 3, 'duck':2}

Давайте просто представим, что у вас есть два условия для теперь, например, 'duck> 0' и 'goat> 2'.

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

def animal_counter(condition1, condition2):
    # I will just show one example here but you would have to make more conditionals
    all_conditions_met = False
    for condition in (condition1, condition2):
        if '>' in condition:
            # by splitting the condition by the conditional symbol
            # we get two useful parts, the animal name and the number
            # which we can compare against the dictionary to get a boolean result
            animal_num = condition.split('>')

        try:
            if dict1[animal_num[0]] > int(animal_num[1]):
                all_conditions_met = True
            else:
                # By putting this return statement here we end the function if one
                # condition returns as False
                all_conditions_met = False
                return all_conditions_met

        except Keyerror:
            if int(animal_num[1]) > 0:
                all_conditions_met = False
                return all_conditions_met
            else:
                all_conditions_met = True

    return all_conditions_met

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

true_rows = []
for row in df.iterrows():
    # row[0] would return the index, row[1] gets the rest of the row
    condition1 = row[1]['condition1']
    condition2 = row[1]['condition2']

    # pass the strings into the animal_counter function
    true_or_false = animal_counter(condition1, condition2)

    if true_or_false:
        true_rows.append(row[0])

Это должно вернуть список индексов всех строк, где выполняются условия.

Вам просто нужно немного расширить его, чтобы он работал для всех возможных условий и больше столбцов.

...