Определить интервалы с определенными значениями

Я новичок в Python и застрял, пытаясь определить «интервалы», где значение y =‹ 70. У меня есть упорядоченная словарная запись:

 d = {0: '92', 11: '70', 43: '77', 44: '76', 61: '77', 64: '69',
                    68: '67', 84: '68', 93: '87', 108: '81', 141: '74'}

Я хочу написать функцию, которая позволяет мне идентифицировать «интервалы» (a, b) на основе ключей (значений x) d на основе значений y = ‹ N. Мои конечные точки (a, b) должны быть там, где значения начать нисходящий и восходящий вход и выход из этого значения N, поэтому фактические значения конечных точек будут «выше» N, но записи между ними должны быть ниже.

 (a,b): {above, below, below, below, above}

Например, меня интересуют интервалы как словарь, здесь N = 70:

{(0,43):{92,70,77}, (61,93): {77, 69, 67, 68, 87}} <-- includes the values at endpoints

Но можно игнорировать те другие «интервалы», где значения никогда не ниже 70, поэтому в этом случае нам не нужно: (43,51), (93,180)

Есть ли простой способ сделать это? До сих пор мне удавалось определить точки, в которых происходит изменение «выше» на «ниже» 70 или наоборот, но я не уверен, как действовать при создании интервалов и значений (например, в словаре). Я думаю, что я смотрел на это слишком долго.


person Lillian Milagros Carrasquillo    schedule 07.03.2012    source источник
comment
В выходных данных примера используется значение y ‹= 70, а не значение y ‹ 70.   -  person Kyss Tao    schedule 08.03.2012
comment
Спасибо! Это была опечатка.   -  person Lillian Milagros Carrasquillo    schedule 08.03.2012
comment
хорошо, обновил мой ответ соответственно   -  person Kyss Tao    schedule 08.03.2012
comment
Я только что снова отредактировал свой ответ, и знак принятия исчез. Вы удалили его? (Мне просто любопытно понять, удалило ли это редактирование, я здесь совсем новичок)   -  person Kyss Tao    schedule 08.03.2012
comment
Я довольно новый! Я принял это по ошибке, когда копировал текст. Я ценю помощь!   -  person Lillian Milagros Carrasquillo    schedule 08.03.2012
comment
Вы говорите: «У меня есть упорядоченная словарная статья». Нет не надо (: Словари не упорядочены, они как хэш-таблица.   -  person jwd    schedule 08.03.2012
comment
Да, но я использую функцию sorted(), чтобы она делала это, когда я перебираю ключи. Но спасибо за информацию!   -  person Lillian Milagros Carrasquillo    schedule 08.03.2012


Ответы (5)


Следующий код должен дать вам результат, который вы просили:

oninterval = False
dd = {}
keys = d.keys()
keys.sort()
start_key, first_val = keys[0], d[keys[0]]

for k in keys:
    v = float(d[k])
    if oninterval:
        cur_list.append(v)
        if not int(v) <= 70: # interval ends
            oninterval = False
            dd[(start_key,k)] = cur_list
    else:
        if int(v) <= 70:
            cur_list = [first_val, v]
            oninterval = True
        else:
            start_key, first_val = k, v

if oninterval: dd[(start_key, keys[-1])] = cur_list

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

Расширенная дробь кода для принятия первого или последнего элемента со значением y ‹= 70 и обработки значений y как чисел с плавающей запятой.

person Kyss Tao    schedule 08.03.2012
comment
Это похоже на то, как я начал писать этот цикл, поэтому он близок к моей логике. Спасибо, что сделали этот длинный день немного короче. - person Lillian Milagros Carrasquillo; 08.03.2012
comment
Если я передам этому новый словарь d, скажем, d = {0: '78', 35: '61.33330154', 172: '65', 110: '59.66669846'}, то вывод будет {(141, 0): [ 74,0, 69,0, 78,0]}. Не уверен, почему это происходит, но это (141,0) что-то странное. Возможно, это проблема с мета-конечными точками. Например, у меня есть значения x от 0 до 180, так что же происходит, когда есть нижняя точка прямо на краю, как здесь, при 172 (значение равно 65)? - person Lillian Milagros Carrasquillo; 08.03.2012
comment
В случае, когда мне пришлось бы расширить этот вывод (0,172) до (0,180), где мне изменить код? Спасибо! - person Lillian Milagros Carrasquillo; 08.03.2012
comment
Я не совсем уверен, что вы имеете в виду. Учитывая d = {0: '78', 35: '61.33330154', 172: '65', 110: '59.66669846'}, какой результат вы хотите получить? - person Kyss Tao; 08.03.2012
comment
давайте продолжим это обсуждение в чате - person Lillian Milagros Carrasquillo; 08.03.2012
comment
Не уверен, почему я получаю этот вывод сейчас? д = {0: 78,0, 35: 61,33330154, 172: 65,0, 110: 59,66669846}; Выход = {(0, 0): [74.0, 69.0, 78.0], (0, 172): [78.0, 61.33330154, 59.66669846, 65.0, 100.3330002]} Откуда берется 0,0? - person Lillian Milagros Carrasquillo; 08.03.2012

По причинам, которые я не могу полностью объяснить, эта проблема загипнотизировала меня. Но я думаю, что наконец-то выкинул это из своей системы. Во-первых, основное, чистое и простое решение:

intervals = [[]]
prev = None
sorted_items = sorted(d.iteritems())
for k, v in sorted_items:
    if v <= 70:
        ext = (k,) if (intervals[-1] or prev is None) else (prev, k)
        intervals[-1].extend(ext)
    elif intervals[-1]:
        intervals[-1].append(k)
        intervals.append([])
    prev = k

if not intervals[-1]:
    intervals.pop()

print dict(((iv[0], iv[-1]), [d[k] for k in iv]) for iv in intervals)

Достаточно просто абстрагироваться от приведенного выше, чтобы создать итератор:

def iter_intervals(vals, filter_f, _nil=object()):
    prev = _nil
    interval = []
    for x in vals:
        if filter_f(x):
            ext = (x,) if (interval or prev is _nil) else (prev, x)
            interval.extend(ext)
        elif interval:
            interval.append(x)
            yield interval
            interval = []
        prev = x
    if interval: 
        yield interval

intervals = iter_intervals(d.iteritems(), lambda x: x[1] <= 70)
print dict(((iv[0][0], iv[-1][0]), [v for k, v in iv]) for iv in intervals)

Но это должно хранить много состояния. Интересно, есть ли способ сделать меньше этого...

def iter_intervals(vals, filter_f, _nil=object()):
    iters = itertools.tee(itertools.chain((_nil,), vals, (_nil,)), 3)
    next(iters[1]); next(iters[2]); next(iters[2])
    triplets = itertools.izip(*iters)
    interval = set()
    for p, curr, n in triplets:
        if filter_f(curr):
            interval.update((p, curr, n))
        elif interval:
            interval.discard(_nil)
            yield sorted(interval)
            interval = set()
    if interval:
        interval.discard(_nil)
        yield sorted(interval)

intervals = iter_intervals(d.iteritems(), lambda x: x[1] <= 70)
print dict(((iv[0][0], iv[-1][0]), [v for k, v in iv]) for iv in intervals)

Сделав это, теперь более очевидно, как адаптировать решение ninjagecko, чтобы избежать проблемы просмотра вперед/назад, которая заставляла его сохранить список:

def framed_intervals(points, filter_f, _nil=object()):
    iters = itertools.tee(itertools.chain((_nil,), points, (_nil,)), 3)
    next(iters[1]); next(iters[2]); next(iters[2])
    triplets = itertools.izip(*iters)
    for below, group in itertools.groupby(triplets, lambda x: filter_f(x[1])):
        if below:
            interval = set(itertools.chain.from_iterable(group))
            interval.discard(_nil)   # or continue if None in interval to
            yield sorted(interval)   # drop incomplete intervals

intervals = framed_intervals(d.iteritems(), lambda x: x[1] <= 70)
print dict(((iv[0][0], iv[-1][0]), [v for k, v in iv]) for iv in intervals)
person senderle    schedule 08.03.2012
comment
Спасибо за публикацию. Сегодня я проверю, как это работает. Я тоже немного одержим этой проблемой! Кажется, это довольно распространенная проблема, поскольку все, что я делаю, это идентифицирую интервалы, и я удивлен, что не нашел модуля, который охватывает это. - person Lillian Milagros Carrasquillo; 08.03.2012
comment
Хорошо, это имеет смысл для меня, и все примеры, которые я привожу, похоже, работают. У меня возникли проблемы с его адаптацией в одном конкретном случае, если последнее значение равно ‹=70, интервал должен быть расширен до конца значений x (то есть, если n=180 и d[id][last] ‹70 и last ‹ 180, вывод интервала должен быть (a,180), а не (a,last).Для реального примера:d['150','172'] = {73,55,55}, тогда это действительно должно быть что вместо этого интервал равен (150 180) (где 180 - максимальное значение x). - person Lillian Milagros Carrasquillo; 09.03.2012
comment
@LillianMilagrosCarrasquillo, я не совсем понимаю. Что за a выше? Покажите мне входной dict/последовательность, которая вызывает проблему, и результат, который вы ожидаете. Кроме того, я заметил ошибку в третьей версии; Я забыл выбросить _nil часового. - person senderle; 09.03.2012

Вот несколько подробное решение:

import collections

values = [(0, '92'), (11, '70'), (43, '77'), (44, '76'), (61, '77'), (64, '69'),
   (68, '67'), (84, '68'), (93, '87'), (108, '81'), (141, '74')]
d = collections.OrderedDict(values)

def intervals(d, n):
    result = collections.OrderedDict()
    interval = list()
    lastk, lastv, startk = None, None, None
    for k, v in d.iteritems():
        if int(v) > n:
            if startk is not None:
                interval.append(int(d[k]))
                result[(startk, k)] = interval
                interval = list()
                startk = None
        else:
            if lastv:
                interval.append(int(d[lastk]))
                startk = lastk
            interval.append(int(d[k]))
        lastk, lastv = k, int(v) > n
    return result


if __name__ == '__main__':
    print intervals(d, 70)

Когда я запускаю это, он печатает:

OrderedDict([((0, 43), [92, 70, 77]), ((61, 93), [77, 69, 67, 68, 87])])

что является желаемым результатом.

person srgerg    schedule 08.03.2012

примечание: в вашем словаре есть строковые значения, а не значения типа int. В вашем примере вы можете иметь в виду ‹=, а не ‹.

Итак, чтобы более четко сформулировать вашу проблему, вы:

  • Иметь упорядоченный список точек (x,y)
  • Иметь пороговое значение T
  • Требуется найти все последовательные прогоны точек pi,...,pj, такие, что конечные точки > T, а другие точки — нет; то есть все участки, где точки «впадают и выходят» из пороговой линии. (Обратите внимание, что такие запуски могут перекрываться, например, [71,70,{71],70,71})

Алгоритм будет следующим:

from itertools import *

def dippingIntervals(points, threshold=70):
    yBelowThreshold = lambda i: points[i][1]<=threshold

    for below,g in groupby(range(len(points)), yBelowThreshold):
        if below:
            interval = list(g)
            start,end = interval[0],interval[-1]
            if start>0 and end<len(points)-2:     #modify if "open" intervals also desired
                yield points[start-1 : end+2]

Демо:

>>> d = [(0, 92), (11, 70), (43, 77), (44, 76), (61, 77), (64, 69), (68, 67), (84, 68), (93, 87), (108, 81), (141, 74)]
>>> pprint(list( dippingIntervals(d) ))
[((0, 92), (11, 70), (43, 77)),
 ((61, 77), (64, 69), (68, 67), (84, 68), (93, 87))]

Вы можете без особых хлопот обработать данные, например, чтобы получить их в нужном формате, измените приведенную выше функцию следующим образом:

... yield (start,end), {xy[1] for xy in points[start-1 : end+2]}

Недостатком этого метода является то, что он не работает с итераторами; следующее будет работать на итераторах и является более "классическим" способом сделать это:

def getY(point):
    return point[1]

def dippingIntervals(points, threshold=70, key=getY):
    """
        Returns runs of points whose y-values dip below intervals
        >>> list( dippingIntervals([71,70,74,64,64,70,71], key=lambda x:x) )
        [(71, [70], 74), 
         (74, [64, 64, 70], 71)]
    """
    def match(point):
        return key(point)<=threshold

    lastP = None
    for p in points:
        if lastP==None:
            lastP = p
            continue

        if not match(lastP) and match(p):
            start = lastP
            R = [p]
        elif match(lastP) and match(p):
            R += [p]
        elif match(lastP) and not match(p):
            end = p
            yield start,R,end

        lastP = p
person ninjagecko    schedule 08.03.2012
comment
Спасибо! Итак, хочу ли я создать словарь с кортежами вместо того, как он у меня есть сейчас? То есть d = {0: '78', 35: '61,33330154', 172: '65', 110: '59,66669846'} должно быть d = [(0,78), (35,61,33330154), (172, 65), (110, 59,66669846)]? Я довольно новичок в Python и не сталкивался с этим. - person Lillian Milagros Carrasquillo; 08.03.2012
comment
@LillianMilagrosCarrasquillo: в этом нет необходимости, так как вы можете просто перебирать свой словарь следующим образом: d.items(), который возвращает итератор по (key,value) парам в вашем списке в виде кортежей. Итак, dippingIntervals(d.items()). Обратите внимание, что возвращаемый порядок является произвольным, но в вашем случае вы используете упорядоченный словарь. Лично я бы в первую очередь избегал использования строк, но если уже слишком поздно что-то менять, вы можете сделать d = {k:float(v) for k,v in d.items()}. - person ninjagecko; 08.03.2012
comment
Благодарю. Я пробую другие возможные d и получаю что-то странное. Есть мысли о том, что происходит? ************************ ››› c = {k:float(v) for k,v в d.items()}››› c {0: 78.0, 35: 61.33330154, 172: 65.0, 110: 59.66669846} ››› a = list(dippingIntervals(sorted(c.items())))››› a [] - person Lillian Milagros Carrasquillo; 08.03.2012
comment
@LillianMilagrosCarrasquillo: да, это то, о чем я говорил в своем комментарии #modify if "open" intervals also desired. Когда вы определяли постановку задачи, вы специально исключали интервалы, заканчивающиеся концами вашего списка точек. Например, вы специально исключили интервалы, где левая и правая границы не определены. Если вам нужны эти интервалы, вы можете изменить любой алгоритм, добавив None в конец списка и немного изменив логику. - person ninjagecko; 08.03.2012
comment
например чтобы изменить первый алгоритм, сделайте yBelowThreshold = lambda i: False if None else points[i][1]<=threshold, points = [None]+points+[None], и вы можете удалить оператор if. - person ninjagecko; 08.03.2012
comment
@ninjagecko, решение groupby действительно умное; +1. Но посмотрите последнюю часть моего ответа, чтобы узнать, как сделать его удобным для итератора. - person senderle; 08.03.2012
comment
@senderle: Симпатично, идея триплета была именно тем, как я собирался реализовать ее, чтобы она была удобной для итераторов. Но я думал, что это было бы уродливее; Я не думал использовать set. На самом деле я не очень возражаю против преобразования итератора в список, я думал, что эта проблема может выиграть от того, что она будет дружественной к потоку. - person ninjagecko; 08.03.2012

person    schedule
comment
Это замечательно! Я знал, что есть простой способ, которого я не видел после того, как провел слишком много времени за компьютером. Один вопрос: есть ли способ обрабатывать d как словарь, как в моем примере, или мне нужно работать над преобразованием его в кортежи? У меня есть данные в словаре прямо сейчас. - person Lillian Milagros Carrasquillo; 08.03.2012
comment
Когда я запускаю это, он печатает [[(0, 92), (11, 70), (43, 77)], [(44, 76), (61, 77)], [(93, 87), (108, 81)]], но не должен ли результат быть [[(0, 92), (11, 70), (43, 77)], [(61, 77), (64, 69), (68, 67), (84, 68), (93, 87)]]? - person srgerg; 08.03.2012
comment
@LillianMilagrosCarrasquillo Теперь я научился работать со словарем. Также вы получаете вывод, как описано в вашем вопросе. - person Manish; 08.03.2012