Нахождение количества относительных и абсолютных колебаний в кадре данных, где каждая строка содержит ряды - PullRequest
0 голосов
/ 04 июля 2019

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

  • ID этого временного ряда
  • значение Target (против которогомы хотим измерить отклонения, как относительные, так и абсолютные)
  • и временную серию значений для различных дат: 1/01, 1/02, 1/03, ...

Мы хотим рассчитать число колебаний, какотносительный и абсолютный, для каждой строки / идентификатора временных рядов.Затем мы хотим выяснить, какая строка / идентификатор имеет наибольшее количество колебаний / «пиков», следующим образом :

  • Сначала мы находим разницу между двумя значениями временных рядов и оцениваем порог .Порог показывает, какая разница между двумя значениями допустима, прежде чем мы объявим, что это «колебание» или «всплеск».Если разница превышает установленное вами пороговое значение, то между значениями любых двух столбцов возникает скачок.
    • Однако, нам нужно убедиться, что порог является общим и работает как с%, так и с абсолютными значениями между любыми двумя значениями в любой строке .
    • Итак, в основном мы находимпорог в процентной форме (сделайте образованный прогноз), поскольку у нас есть значения в одной строке, представленные в форме «%».Кроме того, форма «%» также будет правильно работать с абсолютным значением.
  • Выходными данными должны быть новые значения столбцов (FCount), относительные и абсолютные, длякаждая строка / ID.

Код:

import pandas as pd 

# Create sample dataframe
raw_data = {'ID': ['A1', 'B1', 'C1', 'D1'], 
  'Domain': ['Finance', 'IT', 'IT', 'Finance'], 
  'Target': [1, 2, 3, 0.9%], 
  'Criteria':['<=', '<=', '>=', '>='],
  "1/01":[0.9, 1.1, 2.1, 1],
  "1/02":[0.4, 0.3, 0.5, 0.9], 
  "1/03":[1, 1, 4, 1.1], 
  "1/04":[0.7, 0.7, 0.1, 0.7],
  "1/05":[0.7, 0.7, 0.1, 1], 
  "1/06":[0.9, 1.1, 2.1, 0.6],}

df = pd.DataFrame(raw_data, columns = ['ID', 'Domain', 'Target','Criteria', '1/01', 
  '1/02','1/03', '1/04','1/05', '1/06'])

   ID   Domain  Target Criteria  1/01  1/02  1/03  1/04  1/05  1/06  
0  A1  Finance       1       <=   0.9   0.4   1.0   0.7   0.7   0.9  
1  B1       IT       2       <=   1.1   0.3   1.0   0.7   0.7   1.1  
2  C1       IT       3       >=   2.1   0.5   4.0   0.1   0.1   2.1  
3  D1  Finance     0.9%      >=   1.0   0.9   1.1   0.7   1.0   0.6

А вот ожидаемый результат со столбцом счетчика флуктуаций (FCount).Затем мы можем получить любой идентификатор с наибольшим FCount.

   ID   Domain  Target Criteria  1/01  1/02  1/03  1/04  1/05  1/06  FCount
0  A1  Finance       1       <=   0.9   0.4   1.0   0.7   0.7   0.9    -
1  B1       IT       2       <=   1.1   0.3   1.0   0.7   0.7   1.1    - 
2  C1       IT       3       >=   2.1   0.5   4.0   0.1   0.1   2.1    - 
3  D1  Finance     0.9%      >=   1.0   0.9   1.1   0.7   1.0   0.6    -

Ответы [ 4 ]

3 голосов
/ 09 июля 2019

Учитывая,

# importing pandas as pd 
import pandas as pd
import numpy as np

# Create sample dataframe
raw_data = {'ID': ['A1', 'B1', 'C1', 'D1'], 
'Domain': ['Finance', 'IT', 'IT', 'Finance'], 
'Target': [1, 2, 3, '0.9%'],
'Criteria':['<=', '<=', '>=', '>='],
"1/01":[0.9, 1.1, 2.1, 1],
"1/02":[0.4, 0.3, 0.5, 0.9], 
"1/03":[1, 1, 4, 1.1], 
"1/04":[0.7, 0.7, 0.1, 0.7],
"1/05":[0.7, 0.7, 0.1, 1], 
"1/06":[0.9, 1.1, 2.1, 0.6],}



df = pd.DataFrame(raw_data, columns = ['ID', 'Domain', 'Target','Criteria', '1/01', 
'1/02','1/03', '1/04','1/05', '1/06'])

Эту проблему легче решить, разбив ее на две части (абсолютные пороги и относительные пороги) и пройдя ее шаг за шагом по базовым массивам numpy.


РЕДАКТИРОВАТЬ: подробное объяснение впереди, пропустите до конца только конечную функцию

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

date_columns = ['1/01', '1/02','1/03', '1/04','1/05', '1/06']
df[date_columns].values
#Output:
array([[0.9, 0.4, 1. , 0.7, 0.7, 0.9],
       [1.1, 0.3, 1. , 0.7, 0.7, 1.1],
       [2.1, 0.5, 4. , 0.1, 0.1, 2.1],
       [1. , 0.9, 1.1, 0.7, 1. , 0.6]])

Затем мы можем использовать np.diff , чтобы легко получить различия между датами в базовом массиве.Мы также возьмем абсолют, потому что это то, что нас интересует.

np.abs(np.diff(df[date_columns].values))
#Output:
array([[0.5, 0.6, 0.3, 0. , 0.2],
       [0.8, 0.7, 0.3, 0. , 0.4],
       [1.6, 3.5, 3.9, 0. , 2. ],
       [0.1, 0.2, 0.4, 0.3, 0.4]])

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

abs_threshold = 0.5
np.abs(np.diff(df[date_columns].values)) > abs_threshold
#Output:
array([[False,  True, False, False, False],
       [ True,  True, False, False, False],
       [ True,  True,  True, False,  True],
       [False, False, False, False, False]])

Мы можем видеть, что сумма по этому массиву для каждой строки даст нам нужный нам результат (сумма по логическим массивам использует базовые значения True = 1 и False = 0. Таким образом, выэффективно подсчитывая, сколько True присутствует).Для пороговых значений в процентах нам просто нужно сделать дополнительный шаг, разделив все различия с исходными значениями перед сравнением.Собираем все вместе.

Для уточнения:

Мы можем видеть, как сумма по каждой строке может дать нам подсчет значений, пересекающих абсолютный порог, следующим образом.

abs_fluctuations = np.abs(np.diff(df[date_columns].values)) > abs_threshold
print(abs_fluctuations.sum(-1))
#Output:
[1 2 4 0]

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

dates = df[date_columns].values #same as before, but just assigned
differences = np.abs(np.diff(dates)) #same as before, just assigned
pct_threshold=0.5 #aka 50%
print(differences.shape) #(4, 5) aka 4 rows, 5 columns if you want to think traditional tabular 2D shapes only
print(dates.shape) #(4, 6) 4 rows, 6 columns

Теперь обратите внимание, что массив разностей будет иметь на 1 меньше столбцов, что также имеет смысл.потому что для 6 дат будет 5 «разниц», по одной на каждый пробел.

Теперь, просто сосредоточившись на 1 строке, мы видим, что вычислить процентные изменения просто.

print(dates[0][:2]) #for first row[0], take the first two dates[:2]
#Output:
array([0.9, 0.4])
print(differences[0][0]) #for first row[0], take the first difference[0]
#Output:
0.5

изменение от 0.9 to 0.4 - это изменение 0.5 в абсолютном выражении.но в процентном выражении это изменение 0.5/0.9 (разница / оригинал) * 100 (где я упустил умножение на 100, чтобы упростить задачу) или 55.555% или 0.5555 ..

Главное, что нужно понять на этом этапе, - это то, что нам нужно выполнить это деление по «исходным» значениям для всех различий, чтобы получить процентные изменения.Тем не менее, массив дат имеет один «столбец» слишком много.Итак, мы делаем простой срез.

dates[:,:-1] #For all rows(:,), take all columns except the last one(:-1).
#Output:
array([[0.9, 0.4, 1. , 0.7, 0.7],
       [1.1, 0.3, 1. , 0.7, 0.7],
       [2.1, 0.5, 4. , 0.1, 0.1],
       [1. , 0.9, 1.1, 0.7, 1. ]])

Теперь я могу просто рассчитать относительные или процентные изменения по поэлементному делению

relative_differences = differences / dates[:,:-1]

И затем, то же самое, что и раньше.выберите порог, посмотрите, пересекается ли он

rel_fluctuations = relative_differences > pct_threshold
#Output:
array([[ True,  True, False, False, False],
       [ True,  True, False, False,  True],
       [ True,  True,  True, False,  True],
       [False, False, False, False, False]])

Теперь, если мы хотим рассмотреть, пересекается ли либо один из абсолютных или относительных порогов, нам просто нужновзять побитовое ИЛИ | (оно даже есть в предложении!) и затем взять сумму по строкам.

Собрав все это вместе, мы можем просто создать функцию, которая готова к использованию.Обратите внимание, что функции не являются чем-то особенным, это просто способ группировки строк кода для простоты использования.использовать функцию так же просто, как вызвать ее, вы использовали функции / методы, не осознавая этого все время.


date_columns = ['1/01', '1/02','1/03', '1/04','1/05', '1/06'] #if hardcoded.
date_columns = df.columns[5:] #if you wish to assign dynamically, and all dates start from 5th column.

def get_FCount(df, date_columns, abs_threshold=0.5, pct_threshold=0.5):
    '''Expects a list of date columns with atleast two values.
        returns a 1D array, with FCounts for every row.
        pct_threshold: percentage, where 1 means 100%
    '''
    dates = df[date_columns].values
    differences = np.abs(np.diff(dates))
    abs_fluctuations = differences > abs_threshold
    rel_fluctuations = differences / dates[:,:-1] > pct_threshold
    return (abs_fluctuations | rel_fluctuations).sum(-1) #we took a bitwise OR. since we are concerned with values that cross even one of the thresholds.

df['FCount'] = get_FCount(df, date_columns) #call our function, and assign the result array to a new column
print(df['FCount'])
#Output:
0    2
1    3
2    4
3    0
Name: FCount, dtype: int32
2 голосов
/ 09 июля 2019

Предполагая, что вы хотите pct_changes() по всем столбцам строки с пороговым значением, вы также можете попробовать pct_change() при axis=1:

thresh_=0.5
s=pd.to_datetime(df.columns,format='%d/%m',errors='coerce').notna() #all date cols
df=df.assign(Count=df.loc[:,s].pct_change(axis=1).abs().gt(0.5).sum(axis=1))

Или:

df.assign(Count=df.iloc[:,4:].pct_change(axis=1).abs().gt(0.5).sum(axis=1))

   ID   Domain  Target Criteria  1/01  1/02  1/03  1/04  1/05  1/06  Count
0  A1  Finance     1.0       <=   0.9   0.4   1.0   0.7   0.7   0.9      2
1  B1       IT     2.0       <=   1.1   0.3   1.0   0.7   0.7   1.1      3
2  C1       IT     3.0       >=   2.1   0.5   4.0   0.1   0.1   2.1      4
3  D1  Finance     0.9       >=   1.0   0.9   1.1   0.7   1.0   0.6      0
1 голос
/ 11 июля 2019

Следующее является намного более чистой идиомой панд и улучшает версию @ ParitoshSingh.Гораздо чище хранить два отдельных фрейма данных:

  • a ts (метаданные) фрейма данных для столбцов временных рядов «ID», «Домен», «Цель», «Критерии»
  • a values фрейм данных для значений временных рядов (или «даты», поскольку OP продолжает их вызывать)
  • и использование ID в качестве общего индекса для обоих фреймов данных, теперь вы получаете бесшовное объединение / объединение, а такжедля любых результатов, например, когда мы вызываем compute_FCounts().
  • , теперь нет необходимости передавать некрасивые списки имен столбцов или индексов (в compute_FCounts()).Это намного лучше дедупликации, как указано в комментариях.Код для этого находится внизу.

При этом compute_FCount просто уменьшается до четырехстрочного (и я улучшил версию @ ParitoshSingh для использования встроенных панд df.diff(axis=1), а затем панд .abs(); также обратите внимание, что полученная серия возвращается с правильным индексом ID, а не 0:3; следовательно, может использоваться непосредственно в присваивании / вставке / слиянии / объединении):

def compute_FCount_df(dat, abs_threshold=0.5, pct_threshold=0.5):
    """"""Compute FluctuationCount for all timeseries/rows""""""
    differences = dat.diff(axis=1).iloc[:, 1:].abs()
    abs_fluctuations = differences > abs_threshold
    rel_fluctuations = differences / dat.iloc[:,:-1] > pct_threshold
    return (abs_fluctuations | rel_fluctuations).sum(1)

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

Также обратите внимание, что лучше не помещать серию / столбец fcounts ни в values (где он определенно не принадлежит), либо в ts (где этобыло бы немного глупо).Обратите внимание, что

#ts['FCount'] 
fcounts = compute_FCount_df(values)

>>> fcounts
A1    2
B1    2
C1    4
D1    1

и это позволяет вам напрямую получить индекс (ID) временных рядов с большинством «колебаний»:

>>> fcounts.idxmax()
'C1'

Но на самом деле, так как концептуально мыЧтобы применить функцию отдельно по строкам к каждой строке значений временных рядов, мы должны использовать values.apply(..., axis=1):

values.apply(compute_FCount_ts, axis=1, reduce=False) # 

def compute_FCount_ts(dat, abs_threshold=0.5, pct_threshold=0.5):
    """Compute FluctuationCount for single timeseries (row)"""
    differences = dat.diff().iloc[1:].abs()
    abs_fluctuations = differences > abs_threshold
    rel_fluctuations = differences / dat.iloc[:,:-1] > pct_threshold
    return (abs_fluctuations | rel_fluctuations).sum(1)

(Примечание: все еще пытаюсь отладить "Слишком«Многие пандеры» выдают панд )

Наконец, вот примерный код для установки двух отдельных фреймов данных с общим индексом ID:

import pandas as pd
import numpy as np

ts = pd.DataFrame(index=['A1', 'B1', 'C1', 'D1'], data={
    'Domain': ['Finance', 'IT', 'IT', 'Finance'],
    'Target': [1, 2, 3, '0.9%'],
    'Criteria':['<=', '<=', '>=', '>=']})

values = pd.DataFrame(index=['A1', 'B1', 'C1', 'D1'], data={
    "1/01":[0.9, 1.1, 2.1, 1],
    "1/02":[0.4, 0.3, 0.5, 0.9],
    "1/03":[1, 1, 4, 1.1],
    "1/04":[0.7, 0.7, 0.1, 0.7],
    "1/05":[0.7, 0.7, 0.1, 1],
    "1/06":[0.9, 1.1, 2.1, 0.6]})
1 голос
/ 08 июля 2019

Попробуйте loc и iloc и sub и abs и sum и idxmin:

print(df.loc[df.iloc[:, 4:].sub(df['Target'].tolist(), axis='rows').abs().sum(1).idxmin(), 'ID'])

Выход:

D1

Объяснение:

  • Сначала я получаю столбцы, начиная с четвертого, а затем просто вычитаю каждую строку с соответствующим столбцом Target.

  • Затем получите его абсолютное значение, так что -1.1 будет 1.1, а 1.1 будет по-прежнему 1.1, затем sum каждая строка вместе и получите строку с наименьшим числом.

  • Затем используйте loc, чтобы получить этот индекс в фактическом фрейме данных и получить столбец ID, который дает вам D1.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...