выбор подходящей модели архитектуры / дизайна для минимального рабочего примера - PullRequest
2 голосов
/ 11 октября 2019

Мне было поручено разработать сценарий, который считывает некоторые данные в формате csv и некоторые статистические параметры в формате .ini, выполняет некоторые статистические тесты для этих данных и выводит новый файл csv с новыми столбцами, содержащими результаты статистическоготесты для каждого ряда. Одной из важных функций является возможность выбора «диапазонов» строк из данных таким образом, чтобы пользователь мог контролировать, какой диапазон столбцов используется для статистических тестов.

Я записал свою программу (которая намного сложнее) в следующем примере с игрушкой. Пример с игрушкой не имеет смысла с научной точки зрения, но я думаю, что он иллюстрирует функциональность, которую я пытаюсь достичь очень хорошо:

Код Python выглядит следующим образом:

from configobj import ConfigObj
import pandas as pd
from scipy import stats

config = ConfigObj('input_mwe.ini')
crit_slope = config.get('data').as_float('critical_slope')

start_col = config['subset']['start_col']
end_col = config['subset']['end_col']

t = list(range(int(start_col), int(end_col)+1))

intercept_list = []
slope_list = []
decision_list = []

df = pd.read_csv('input.csv')

for idx, row in df.iterrows():
    y = list(row[start_col:end_col])
    slope, intercept, r_value, p_value, std_err = stats.linregress(t, y)
    intercept_list.append(intercept)
    slope_list.append(slope)
    if slope<crit_slope:
        decision_list.append('accept')
    else:
        decision_list.append('reject')

df['intercept'] = intercept_list
df['slope'] = slope_list
df['decision'] = decision_list

df.to_csv("output.csv")

input_mwe.ini имеет следующее содержимое:

[data]
    critical_slope=0.2
[subset]
    start_col=3
    end_col=8

input.csv имеет следующее содержимое

Point_id,0,1,2,3,4,5,6,7,8,9
1,5,4,6,8,5,4,1,8,7,4
2,2,5,6,8,7,4,5,6,2,4
3,5,7,8,4,5,6,8,7,4,1
4,5,4,6,6,6,8,7,8,6,5
5,5,4,4,4,5,8,7,9,5,2

Вывод выглядит следующим образом:

,Point_id,0,1,2,3,4,5,6,7,8,9,intercept,slope,decision
0,1,5,4,6,8,5,4,1,8,7,4,5.3428571428571425,0.02857142857142857,accept
1,2,2,5,6,8,7,4,5,6,2,4,10.36190476190476,-0.9142857142857143,accept
2,3,5,7,8,4,5,6,8,7,4,1,4.40952380952381,0.22857142857142856,reject
3,4,5,4,6,6,6,8,7,8,6,5,6.0476190476190474,0.14285714285714282,accept
4,5,5,4,4,4,5,8,7,9,5,2,3.819047619047619,0.45714285714285713,reject

Сейчас на самом деле я выполняю гораздо больше статистических тестов, и решение о принятии или отклонении основано на других параметрах. Кроме того, метод для подстановки столбцов из входного CSV немного отличается.

Однако, это не имеет отношения к моему вопросу. Я хотел бы предложить простой, хорошо узнаваемый и хорошо протестированный шаблон проектирования, который может позволить мне воспроизвести вышеупомянутые функциональные возможности с использованием объектно-ориентированного кода. У меня очень мало опыта разработки шаблонов проектирования (хотя я раньше много работал с классами и объектами и знаю, как их создавать).

Что было бы хорошей практической отправной точкой для рефакторинга вышеупомянутого в объекториентированный код? Какую модель дизайна использовать? Могу ли я использовать более одного шаблона проектирования?

Большое спасибо за вашу помощь, так как мне не терпится узнать больше об организации моего кода таким образом.

1 Ответ

0 голосов
/ 11 октября 2019

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

Сначала небольшой класс для настройки.

from configobj import ConfigObj

class Config:

  def __init__(self, config_file : str):
      self.config = ConfigObj(config_file)
      self.crit_slope = config.get('data').as_float('critical_slope')
      self.start_col = int(config['subset']['start_col'])
      seld.end_col = int(config['subset']['end_col']) + 1

Затем нам нужен класскоторые возвращают часть кадра данных на основе условия.

import pandas as pd 

class DataSelector:

     def __init__(self, csv_file, config_file):
         self.df =  pd.read_csv(csv_file)
         self.config = Config(config_file)

     @property
     def by_column(self):
         return self.df.iloc[:,self.config.start_col:self.config.end_col].copy()

     @property 
     def get_x_y(self):
        for idx, row in self.by_column.iterrows():
            yield row.tolist(), range(self.config.start_col, self.config.end_col)

Мы хотим иметь статистический класс для отделения анализа от конфигурации и выбора данных


from scipy import stats

class MyStats:

    @staticmethod
     def critic_slope(x, y, critic):
         slope, intercept, *_ = stats.lineregress(x,y)
         if slope < critic:
             decision = "Accepted"
         else:
             decision = "Rejected"
         return slope, intercept, decision 

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

from collections import defaultdict

def analyse_critical(csv_file, ini_file, out_file):
    ds = DataSelector(csv_file, ini_file)
    df = df.by_column
    final_df = defaultdict(list)
    for x, y in ds.get_x_y:
        slope, intercept, decision = MyStats.critic_slope(x, y, ds.config.crit_slope)
        final_df["slope"].append(slope)
        final_df["intercept"].append(intercept)
        final_df["decision"].append(decision)
    for element in ["slope", "intercept", "decision"]:
        df[element] = final_df[element]
    df.to_csv(out_file)
    return df

def analyse_2(csv_file, ini_file, out_file):
    pass 

def analyse_3(csv_file, ini_file, out_file):
    pass    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...