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

Мотивация:

Я очень часто сталкиваюсь с задачей, когда мне нужно проверить правильность значений фрейма данных. На его основе я могу создать еще один фрейм данных с Правда/Ложь значения, основанные на первом кадре данных. Ниже я демонстрирую пример такой задачи. У меня есть фрейм данных с журналами, и мне нужно проверить, соответствует ли каждое значение определенному диапазону. Если он находится в диапазоне, алгоритм возвращает Правда в соответствующей ячейке нового фрейма данных. По сути, алгоритм может использоваться для обнаружения аномалий в фреймах данных. На простых примерах довольно просто использовать применять методы и встроенный NumPy функции. Bellow — нетривиальный пример, для которого я изо всех сил пытаюсь найти быструю реализацию.

Вход:

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

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

Желаемый результат:

На основе этого фрейма данных я хочу создать новый фрейм данных, где каждое значение Правда или же Ложь на основе определенных правил:

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

Данные:

Данные представлены в формате json data.json:

[
 {
   "Date": "9/20/2020 8:50",
   "UE": 1,
   "DL": 154
 },
 {
   "Date": "9/20/2020 8:50",
   "UE": 2,
   "DL": 123
 },
 {
   "Date": "9/20/2020 8:50",
   "UE": 3,
   "DL": 132
 },
 {
   "Date": "9/20/2020 8:57",
   "UE": 1,
   "DL": 141
 },
 {
   "Date": "9/20/2020 8:57",
   "UE": 8,
   "DL": 151
 },
 {
   "Date": "9/20/2020 8:57",
   "UE": 2,
   "DL": 155
 },
 {
   "Date": "9/20/2020 9:12",
   "UE": 1,
   "DL": 151
 },
 {
   "Date": "9/20/2020 9:12",
   "UE": 5,
   "DL": 154
 },
 {
   "Date": "9/20/2020 9:12",
   "UE": 3,
   "DL": 144
 },
 {
   "Date": "9/20/2020 9:20",
   "UE": 1,
   "DL": 134
 },
 {
   "Date": "9/20/2020 9:20",
   "UE": 4,
   "DL": 155
 },
 {
   "Date": "9/20/2020 9:20",
   "UE": 3,
   "DL": 153
 }
]

Моя реализация:

Я загружаю следующие данные:

def upload_data(file):
    df = pd.read_json(file)
    df['Date'] = pd.to_datetime(df['Date'], format="%Y-%d-%m %H:%M:%S") 
    df['CQI'] = np.nan
    return df
df = upload_data('data.json')

Далее создаю дополнительную строку. Мне удалось сделать это векторизованным способом:

df['CQI'] = (df['Date'] != df['Date'].shift()).cumsum()

Наконец, я создаю фрейм данных с результатами:

def create_df_with_reslts(df):
    df_results = pd.DataFrame().reindex_like(df)
    df_results['Results'] = np.nan
    return df_results
df_results = create_df_with_reslts(df)

А теперь смотрю, как ускорить и векторизовать основную часть кода.

def check_df(df, df_results):

    # check date format
    date_format="%Y-%m-%d %H:%M:%S"
    for index, row in df.iterrows():
        date_string = row['Date']
        try:
            df_results['Date'][index] = True
        except ValueError:
            df_results['Date'][index] = False    

    # checking that number of UEs is between 1 and 500
    df_results['UE'] = np.where((df['UE'] >=1) & (df['UE'] <=500), True, False)
    
    # finding bordes for CQI's spans
    previous_row = df['Date'].astype(str)[0]
    CQI_index = 0
    CQI_list = []
    for index, row in df.iterrows():
        # for same CQI
        if row['Date'] == previous_row:
            CQI_index += 1
            previous_row = row['Date']
        # for next CQI in table
        else:
            CQI_list.append(CQI_index)
            CQI_index = 1
            previous_row = row['Date']
    CQI_list.append(CQI_index)
    
    # checking whether or not borders are correct
    k=0
    for i in range(len(CQI_list)):
        if CQI_list[i] == 6 or CQI_list[i] == 8:
            for j in range(CQI_list[i]):
                df_results['CQI'][j+k] = True     
        else:
            for j in range(CQI_list[i]):
                df_results['CQI'][j+k] = False   
        j=CQI_list[i]
        k=k+j
    
    # Values of DL corresponds to uniform distribution with epsilon 10%
    list_of_columns = ['DL']   
    for n in range(len(list_of_columns)):
        # find the highest number for each CQI (CQI_MAX)
        k=0
        max_list = []  
        for i in range(len(CQI_list)):
            X = df[list_of_columns[n]][k:k+CQI_list[i]+1]
            max_list.append(X[X == X.max()].iloc[0])
            k=k+CQI_list[i]
        # check that each value in [max_list*10/100, max_list]
        k=0
        for i in range(len(CQI_list)):
            for j in range(CQI_list[i]):
                if float(df[list_of_columns[n]][j+k]) >= float(max_list[i])*90/100 and float(df[list_of_columns[n]][j+k]) <= float(max_list[i]):
                    df_results[list_of_columns[n]][j+k] = True
                else:
                    df_results[list_of_columns[n]][j+k] = False
            j=CQI_list[i]
            k=k+j
            
    # final results for each column
    df_results['Results'] = df_results.prod(axis=1).astype(bool)
        
    # return max_list    
    return df_results
%timeit check_df(df, df_results)
# 19.5 ms ± 2.84 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

Я изменил итерацию по фрейму данных на итерацию по его строкам: диапазон (len (df)) в df.iterrows (). Это дало только ~ 20% ускорения. Также один цикл for был изменен на векторизованную операцию (нп. где). Я не знаю, как сделать то же самое для других циклов for.

Мой вопрос:

Можно ли ускорить и векторизовать другие части кода?

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *