как превратить for l oop с оператором if, работающим с datetime df, в понимание списка - PullRequest
1 голос
/ 30 мая 2020

Я пытаюсь превратить приведенное ниже для l oop с оператором if в понимание списка.

# Create dictionary to hold results
    trip_counts = {'AM': 0, 'PM': 0}

# Loop over all trips
for trip in onebike_datetimes:
  # Check to see if the trip starts before noon
  if trip['start'].hour < 12:
    # Increment the counter for before noon
    trip_counts["AM"] += 1
  else:
    # Increment the counter for after noon
    trip_counts["PM"] += 1

Я пробовал

[trip_counts["AM"]+=1 if trip['start'].hour <12 else trip_counts['PM']+= 1 for trip in onebike_datetimes] 

, но продолжаю получать синтаксическую ошибку

Ответы [ 4 ]

1 голос
/ 30 мая 2020

Вы можете использовать понимание списка (на самом деле, просто выражение генератора), но не так, как вы думаете. Создайте генератор AM s и PM s, затем используйте его для создания экземпляра Counter.

from collections import Counter


trip_counts = Counter(("AM" if trip['start'].hour < 12 else "PM") 
                       for trip in onebike_datetimes)

Автономная демонстрация:

from collections import Counter
from types import SimpleNamespace


onebike_datetimes = [
    {'start': SimpleNamespace(hour=9)},
    {'start': SimpleNamespace(hour=3)},
    {'start': SimpleNamespace(hour=14)},
    {'start': SimpleNamespace(hour=19)},
    {'start': SimpleNamespace(hour=7)},
    ]

trip_counts = Counter(("AM" if trip['start'].hour < 12 else "PM") 
                       for trip in onebike_datetimes)

assert trip_counts["AM"] == 3
assert trip_counts["PM"] == 2
0 голосов
/ 30 мая 2020

Если это pandas DataFrame, с которым вы работаете, почему бы не отфильтровать значения и не суммировать их все сразу?

Может сработать что-то вроде этого:

trip_counts['AM'] = len(trip[trip.loc[:, 'hour'] < 12].index)
trip_counts['PM'] = len(trip[trip.loc[:, 'hour'] >= 12].index)


РЕДАКТИРОВАТЬ : Я только что провел несколько тестов на приведенных здесь ответах, учитывая, что некоторые люди думают, что понимание списков автоматически выполняется быстрее.

Как видите, обычный for- l oop более или менее имеет лучшую производительность в этом случае, сопоставимую только с использованием Counter с пониманием списка, как указано в одном из других ответов здесь.

Обратите внимание, что я немного изменил свою реализацию Pandas, чтобы она соответствовала тому, как, по моему мнению, ваши данные могут быть структурированы (т. Е. Не в DataFrame), и поэтому может потребоваться немного больше накладных расходов на преобразование ваших данных в DataFrame при каждом запуске.

benchmark

Код для создания этого графика показан ниже:

import pandas as pd
import numpy as np
from collections import Counter
from types import SimpleNamespace

import perfplot


def gen_data(n):
    onebike_datetimes = [
    {'start': SimpleNamespace(hour=9)},
    {'start': SimpleNamespace(hour=3)},
    {'start': SimpleNamespace(hour=14)},
    {'start': SimpleNamespace(hour=19)},
    {'start': SimpleNamespace(hour=7)},
    {'start': SimpleNamespace(hour=14)},
    {'start': SimpleNamespace(hour=19)},
    {'start': SimpleNamespace(hour=2)},
    {'start': SimpleNamespace(hour=20)},
    {'start': SimpleNamespace(hour=12)},
    ]*n

    return onebike_datetimes


def use_vanilla_for(a):
#     onebike_datetimes = gen_data(n)
    onebike_datetimes = a

    trip_counts = {'AM': 0, 'PM': 0}

    for trip in onebike_datetimes:
        if trip['start'].hour < 12:
            trip_counts["AM"] += 1
        else:
            trip_counts["PM"] += 1
    return 1    
#     return trip_counts


def use_list_comp(a):
#     onebike_datetimes = gen_data(n)
    onebike_datetimes = a

    trip_counts = {'AM': 0, 'PM': 0}

    l = ["AM" if trip["start"].hour < 12 else "PM" for trip in onebike_datetimes]
    trip_counts = {i: l.count(i) for i in l}
    return 1
#     return trip_counts


def use_counter(a):
#     onebike_datetimes = gen_data(n)
    onebike_datetimes = a

    trip_counts = {'AM': 0, 'PM': 0}

    trip_counts = Counter(("AM" if trip['start'].hour < 12 else "PM") 
                       for trip in onebike_datetimes)
    return 1
#     return trip_counts


def use_pandas(a):
#     onebike_datetimes = gen_data(n)
    onebike_datetimes = a

    trip = pd.DataFrame(list(map(lambda a: a['start'].hour, onebike_datetimes)), columns=['hrs'])

    trip_counts = {'AM': 0, 'PM': 0}

    trip_counts['AM'] = len(trip[trip['hrs'] < 12].index)
    trip_counts['PM'] = len(trip[trip['hrs'] >= 12].index)
    return 1
#     return trip_counts

perfplot.show(
    setup=lambda n: gen_data(n),
    kernels=[
        lambda a: use_vanilla_for(a),
        lambda a: use_list_comp(a),
        lambda a: use_counter(a),
        lambda a: use_pandas(a),
    ],
    labels=["vanilla_for", "list_comp", "counter", "dataframe"],
    n_range=[2 ** k for k in range(10)],
    xlabel="len(a)",
)
0 голосов
/ 30 мая 2020

Намного проще сохранить свой for-l oop.

Если вы действительно хотите использовать понимание списка, вы можете сделать что-то вроде этого:

l = ["AM" if trip["start"].hour < 12 else "PM" for trip in onebike_datetimes]
am_count = l.count("AM")
trip_counts = {"AM": am_count, "PM": len(l) - am_count}

(И вам не нужно инициализировать trip_counts, если вы используете это)

0 голосов
/ 30 мая 2020

Задания - это утверждения. Операторы не могут использоваться в понимании списка. Используйте l oop

Вы действительно должны никогда не делать этого, но для полноты:

trip_counts = {'AM': 0, 'PM': 0}

[trip_counts.__setitem__('AM', trip_counts['AM']+1) if trip['start']['hour'] <12 else trip_counts.__setitem__('PM', trip_counts['PM']+1) for trip in onebike_datetimes]

print(f"With list comprehension: {trip_counts}")
OUT: With list comprehension: {'AM': 1, 'PM': 2}
...