<jupyter_start><jupyter_text>Тестовое задание на позицию Data Scientist  1. Data Science \ Data Analysis Необходимо построить модель, которая на основании поступающих каждую минуту данных определяет качество продукции, производимое на единице оборудования.Единица оборудования состоит из 5 одинаковых по размеру камер, в каждой камере установлено по 3 датчика температур. Также имеются данные о высоте слоя сырья и его влажности. Высота слоя и влажность измеряются при входе сырья в машину. Сырье проходит через единицу оборудования за 1 час.Данные с показателями работы оборудования содержатся в файле x_train.csv:| Название тега  | Описание тега || :- | -: || T_data_1_1	| 1-й датчик в 1-й камере || T_data_1_2	| 2-й датчик в 1-й камере || T_data_1_3	| 3-й датчик в 1-й камере || T_data_2_1	| 1-й датчик во 2-й камере || T_data_2_2	| 2-й датчик во 2-й камере || T_data_2_3	| 3-й датчик во 2-й камере || T_data_3_1	| 1-й датчик в 3-й камере || T_data_3_2	| 2-й датчик в 3-й камере || T_data_3_3	| 3-й датчик в 3-й камере || T_data_4_1	| 1-й датчик в 4-й камере || T_data_4_2	| 2-й датчик в 4-й камере || T_data_4_3	| 3-й датчик в 4-й камере || T_data_5_1	| 1-й датчик в 5-й камере || T_data_5_2	| 2-й датчик в 5-й камере || T_data_5_3	| 3-й датчик в 5-й камере || H_data	| Высота слоя || AH_data	| Влажность сырья |Качество продукции измеряется в лаборатории по пробам, которые забираются каждый час, данные по известным анализам содержатся в файле y_train.csv. В файле указано время забора пробы, проба забирается на выходе из единицы оборудования.Вы договорились с заказчиком, что оценкой модели будет являться показатель MAE. Для оценки модели необходимо сгенерировать предсказания за период, указанный в файле x_test.csv (1184 предикта).Результатом является воспроизводимый Python код в данной тетрадке + приложенный csv файл с предсказаниями на тестовой выборке.<jupyter_code>import torch
import pandas as pd
import numpy as np
import seaborn as sns
from tqdm import tqdm
from math import trunc
from datetime import datetime
from matplotlib import pyplot as plt
from torch.utils.data import TensorDataset, DataLoader<jupyter_output><empty_output><jupyter_text>EDA<jupyter_code>x_train = pd.read_csv('data/x_train.csv')
y_train = pd.read_csv('data/y_train.csv')
x_test = pd.read_csv('data/x_test.csv')
x_train.head()
y_train.head()<jupyter_output><empty_output><jupyter_text>По данному описанию качества продукции мы видим, что стандартной отклонение равно 46, запомним это значение в качестве отправной точки для baseline.<jupyter_code>y_train['target'].describe()<jupyter_output><empty_output><jupyter_text>Data preprocessing Для удобства сразу приведем данные о времени из типа string к типу datetime.<jupyter_code>x_train['timestamp'] = pd.to_datetime(x_train['timestamp'])
y_train['timestamp'] = pd.to_datetime(y_train['timestamp'])
x_test['timestamp'] = pd.to_datetime(x_test['timestamp'])<jupyter_output><empty_output><jupyter_text>Данные о качестве продукции начинаются с более позднего времени, чем данные с показателями работы оборудования, поэтому для удобства формирования тренировочного датасета обрежем данные с показателями работы обородувания до начала первого эксперимента, указанного в данных о качестве продукции.<jupyter_code>def get_time_delta(start_time: datetime, end_time: datetime) -> float:
    """Returns time difference in minutes.
    """
    return (end_time - start_time).total_seconds() / 60 if start_time < end_time else -1
def delete_data_before_start(data: pd.Series, start_time: datetime) -> None:
    """Deletes non-actual by time data inplace.
    """
    start = 0
    while (get_time_delta(data['timestamp'].iloc[start], start_time) > 59):
        start += 1

    data.drop(data.index[0:start], inplace=True)
    data.reset_index(drop=True, inplace=True)
delete_data_before_start(x_train, y_train['timestamp'].iloc[0])<jupyter_output><empty_output><jupyter_text>Mean Baseline В качестве baseline рассмотрим следующую конструкцию, которая чуть более сложная, чем выводить просто среднее по таргетам для любого значения. А именно:    - выберем данные с показателями работы для каждого эксперимента (данные за час)    - посчитаем среднее по каждому столбцу для каждого такого набора    - по полученной строчке для эксперимента будем предсказывать значение качества продукции<jupyter_code>def get_base_preprocessed_data(data: pd.Series, end_time: datetime) -> pd.Series:
    """Returns data aggregating mean value per hour.
    
    Args:
        data: Source data.
        end_time: Time of the last experiment.
        
    Returns:
        A dataframe that contains preprocessed data from the corresponding input dataframe.
    """
    cols = list(data)
    cols.remove('Unnamed: 0')
    
    df = pd.DataFrame(columns=cols)
    cols.remove('timestamp')
    
    for idx in tqdm(range(trunc(len(data) / 60))):
        if (get_time_delta(data['timestamp'].iloc[idx], end_time) != -1):
            row = data.loc[60*idx:60*idx+59, cols].mean()
            row['timestamp'] = data['timestamp'].iloc[60*idx+59]
            df.loc[idx] = row
            
    return df
x_train_base_ = get_base_preprocessed_data(x_train, y_train['timestamp'].iloc[-1])
y_train_base = y_train['target']
x_train_base = x_train_base_.iloc[:, 1:]<jupyter_output><empty_output><jupyter_text>Neural network preprocessing В качестве более сложного подхода, чем описанный ранее mean baseline, реализуем архитектуру нейронной сети для данной задачи.Для этого сформируем датасет, где каждый элемент будет представлять собой массив, состоящий из массива, размера $(59, 17)$, содержащий все данные об эксперименте и массива, состоящего из соотвествующего значения качества продукции.<jupyter_code>def get_nn_preprocessed_data(data: pd.Series) -> pd.Series:
    """Returns data aggregating by experiment.
    
    Args:
        data: Source data.
        
    Returns:
        A dataframe that contains preprocessed data from the corresponding input dataframe.
    """
    X = []
    data = data.iloc[:, 2:]

    for idx in tqdm(range(trunc(len(data) / 60))):
        X.append(data.iloc[60*idx:60*idx+59].to_numpy())
    
    return X
X = get_nn_preprocessed_data(x_train)
y = list(map(lambda x: [x], y_train['target']))
tensor_x = torch.Tensor(X) 
tensor_y = torch.Tensor(y)

dataset = TensorDataset(tensor_x,tensor_y)

train_size = int(0.9 * len(dataset))
test_size = len(dataset) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])

train_dataloader = DataLoader(train_dataset, batch_size=256, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=256, shuffle=True)<jupyter_output><empty_output><jupyter_text>Algo<jupyter_code>from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler
from sklearn.compose import TransformedTargetRegressor
from sklearn.metrics import mean_absolute_error
import xgboost as xgb

%load_ext tensorboard
import torch.nn as nn
from torch.autograd import Variable
from torch.utils.tensorboard import SummaryWriter
import datetime, os
results = {}<jupyter_output><empty_output><jupyter_text>Mean В качестве самого простого решения попробуем выводить среднее по таргетам для любого значения.<jupyter_code>mean_score = mean_absolute_error(y_train_base, [y_train_base.mean()]*len(y_train_base))
results['Mean'] = [mean_score]<jupyter_output><empty_output><jupyter_text>Baseline Для решения с датасетом из предобработанных средних для каждого эксперимента возьмем простую линейную модель и xgboost.<jupyter_code>def get_model_results(model, params={}):
    name = type(model.named_steps['model']).__name__
    model = GridSearchCV(model, param_grid=params, scoring='neg_mean_absolute_error', cv=3, verbose=1, n_jobs=-1)
    model.fit(x_train_base, y_train_base)
    results[name]=[-model.best_score_]
    print(results[name])
lr = Pipeline(steps=[('normalize', MinMaxScaler()), ('model', LinearRegression())])
get_model_results(lr)
get_model_results(Pipeline(steps=[('normalize', MinMaxScaler()), ('model', xgb.XGBRegressor())]))<jupyter_output>Fitting 3 folds for each of 1 candidates, totalling 3 fits
[9.070946138424569]<jupyter_text>Neural Network Реализуем несложную архитектуру нейронной сети. \В качестве функции потерь для тренировки будем использовать MSE, так как она дифференцируема.<jupyter_code>class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()

        def block(in_feat, out_feat, normalize=True):
            layers = [nn.Linear(in_feat, out_feat)]
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8))
            layers.append(nn.LeakyReLU(0.2, inplace=True))
            return layers

        self.pred = nn.Sequential(
            *block(1003, 512),
            *block(512, 256),
            *block(256, 16),
            torch.nn.Linear(16, 1),
        )
    
    def forward(self, x):
        x = x.view(x.size(0), -1)
        return self.pred(x)
net = Net()
optimizer = torch.optim.Adam(net.parameters(), lr=0.01)
loss_func = torch.nn.MSELoss()
l1_loss = torch.nn.L1Loss()

writer = SummaryWriter()

for epoch in tqdm(range(48)):
    for step, (batch_x, batch_y) in enumerate(train_dataloader): 
        
        b_x = Variable(batch_x)
        b_y = Variable(batch_y)

        prediction = net(b_x)     

        loss = loss_func(prediction, b_y)  
        writer.add_scalar('MSE/train', loss, step)
        writer.add_scalar('MAE/train', l1_loss(prediction, b_y), step)
        
        optimizer.zero_grad()   
        loss.backward()         
        optimizer.step()    
    
    test_loss = 0 
    with torch.no_grad():
        for data in test_dataloader:
            x, y = data
            pred = net(x)
            test_loss += l1_loss(pred, y)
    test_loss /= len(test_dataloader)
    writer.add_scalar('MAE/test', test_loss, epoch)

torch.save(net.state_dict(), 'model/net.pth')
tensorboard --logdir=runs --host=localhost --port=8080
results['neural_net'] = [test_loss.numpy()]<jupyter_output><empty_output><jupyter_text>Results Визуализируем полученные результаты с помощью таблички, в которой указан метод и его результаты.<jupyter_code>def show_results():
    sorted_res = dict(sorted(results.items(), key=lambda item: item[1][0]))
    v = list(sorted_res.values())
    fst = list(map(lambda x: x[0], v))
    df = pd.DataFrame({"name": list(sorted_res.keys()), "mae": fst})
    with pd.option_context('display.max_rows', None, 'display.max_columns', None, 'display.max_colwidth', None):
        display(df)
show_results()<jupyter_output><empty_output><jupyter_text>Applying best solution По результатам нейронная оказалась сеть наиболее удачным подходом.<jupyter_code>x = get_nn_preprocessed_data(x_test)
tx = torch.Tensor(x) 

pred = net(tx)

x_base = get_base_preprocessed_data(x_test, x_test['timestamp'].iloc[-1])
y_test = pd.DataFrame(columns=list(y_train))

y_test['timestamp'] = x_base['timestamp']
y_test['target'] = np.concatenate(pred.detach().numpy())
y_test['Unnamed: 0'] = y_test.index
y_test.to_csv('data/y_test.csv')<jupyter_output><empty_output><jupyter_text>2. Software Engineering  Напишите класс JsonTransformer для обработки входящих данных в предоставленном файле .json, приведения их к требуемому плоскому формату и сохранению в PostgreSQLПуть к файлу json -> JsonTransformer -> flat table @ PostgreSQLВход - input_example.jsonВыход - плоская структура для записи в psql:`` [{ data_id:"266", data_name:"v.2", data_sellerId:"355", statuses_id:"1766", statuses_name:"КС: 10%", statuses_amount"100", statuses_actionBaseId:"266"}, ...]``(1 строка = 1 элемент внутри ^statuses, statuses_id как уникальный ключ)<jupyter_code>import numpy as np
import psycopg2
from functools import reduce
# "скелет" требуемого класса на Python
import abc
import json


class BaseJsonTransformer(abc.ABC):
    def __init__(self, json_file_path):
        self.json_file_path = json_file_path

    def read_json(self):
        with open(self.json_file_path, 'r') as f:
            return json.load(f)

    @abc.abstractmethod
    def transform_json(self, json_to_transform=None):
        return

    @staticmethod
    def write_to_psql(json, psql_address):
        return
class JsonTransformer(BaseJsonTransformer):
    
    @staticmethod
    def traverse(name, d):
        """Traverses dict into flat structure for psql"""
        res = [{}]
        for k, v in d.items():
            if (k[0] == '^'):
                k = k[1:]
            if isinstance(v, list) and len(v) > 0:
                res_ = []
                for vdict in v:
                    res_.append(JsonTransformer.traverse(k, vdict))
                res_ = np.concatenate(res_)

                nres = []
                for i in res:
                    for j in res_:
                        e = i
                        e.update(j)
                        nres.append(e)     
                res = nres
            elif isinstance(v, dict) and len(v) > 0:
                res = JsonTransformer.traverse(k, v)
            else:
                kname = name + '_' + k if name else k
                for rdict in res:
                    rdict[kname] = v if v else 'null'
        return res
    
    def transform_json(self, json_to_transform=None):
        """Transforms json into flat table for psql."""
        res = []
        json = json_to_transform if json_to_transform else self.read_json()

        for j in json:
            res += JsonTransformer.traverse('', j)
        return res
    
    @staticmethod
    def write_to_psql(json, psql_address="host=localhost dbname=postgres user=postgres password=<PASSWORD>"):
        """Writes json in table at the specified address."""
        conn = psycopg2.connect(psql_address)
        cur = conn.cursor()
        #cur.execute("drop table if exists data")
        
        j = json
        cur.execute("create table if not exists data({})".format(reduce(lambda x, y: x + ' text, ' + y, j[0].keys()) + ' text'))

        for j_ in j:
            query = "insert into data values ({})".format("\'" + reduce(lambda x, y: x + "\'"  + " , " + "\'"  + y, j[0].values()) +  "\'")
            cur.execute(query)
        
        conn.commit()
    
trans = JsonTransformer('data/input_example.json')
j = trans.transform_json()
trans.write_to_psql(j)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10505890505890506, 'fraction_numerical': 0.0148995148995149, 'mean_word_length': 3.7674264948794187, 'pattern_counts': {'":': 2, '<': 58, '<?xml version=': 0, '>': 66, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '29698', 'license': 'MIT', 'n_tokens_mistral': 5285, 'n_tokens_neox': 5471, 'n_words': 1227, 'path': 'ddma_2021_ds_tt.ipynb', 'repo_name': 'TayaPenskaya/biocad_tt', 'size': '14424'}