Архив рубрики: Без рубрики

Как включить и отключить swap на Linux системах

Возможно вы замечали что после долгой работы или после открытия тяжелого приложения или же просто от большого количества вкладок в браузере компьютер начинает заметно подтормаживать. Это означает что самое время посмотреть на использование оперативной памяти. Не начала ли операционная система использовать «файл подкачки», как он называется на windows.
В операционных системах «файл подкачки» или swap является некоторый выделенный объём части жесткого диска (HDD) или твердотельного накопителя (SSD) компьютера, куда начинают сохраняться данные для временного хранения, когда оперативной памяти перестает хватать. Т.е. swap используется как виртуальная память, когда оперативная память системы заполнена, что позволяет системе продолжать работу, даже если ей не хватает физической памяти RAM. Содержимое области подкачки перемещается обратно в оперативную память по мере необходимости. Цель подкачки — предоставить системе возможность справляться с бОльшими рабочими нагрузками, чем доступная оперативная память, избегая сбоев из-за нехватки памяти.

И так, чтобы включить или отключить swap используйте следующие команды:

sudo swapoff -a
sudo swapon -a

Выполнять команды обязательно надо от суперюзера. Одним из примеров использования может быть следующее: swap забит и вы хотите его очистить. Вызываем swapoff и система закинет данные из swap обратно в оперативную память. И тормоза на компьютере прекратятся :)

Далее перечислим причины, почему эти команды могут быть полезны:

  1. Стабильность системы: пространство подкачки позволяет операционной системе справляться с большими рабочими нагрузками, чем объем доступной физической памяти, что позволяет избежать сбоев и обеспечить стабильность системы.
  2. Управление памятью: когда системе не хватает физической памяти, она может перемещать редко используемые данные в пространство подкачки, освобождая место в ОЗУ для более важных данных.
  3. Улучшенная производительность: перемещая данные между ОЗУ и пространством подкачки, операционная система может оптимизировать использование памяти, гарантируя, что наиболее часто используемые данные остаются в ОЗУ, а менее часто используемые данные перемещаются в пространство подкачки.
  4. Поддержка гибернации: некоторые системы Linux используют пространство подкачки для поддержки гибернации, когда все состояние системы сохраняется на диск до выключения системы. Когда система снова включается, сохраненное состояние восстанавливается, что позволяет пользователю возобновить работу с того места, где он остановился.
  5. В заключение, пространство подкачки является важной частью стратегии управления памятью системы Linux, помогая обеспечить стабильность системы, повышенную производительность и поддержку режима гибернации.

Каким образом можно контролировать размер swap на линукс системах:

  1. Настройка пространства подкачки во время установки: в процессе установки системы Linux вы можете указать размер раздела подкачки или создать файл подкачки на жестком диске или SSD.
  2. Изменение пространства подкачки после установки. Вы можете изменить размер существующего раздела подкачки или файла с помощью следующей команды: sudo swapoff -a, а затем sudo swapon -a, чтобы отключить и снова включить подкачку.
  3. Включение и отключение подкачки: вы можете временно включить или отключить пространство подкачки с помощью команд swapon и swapoff соответственно.
  4. Установка swappiness: Параметр swappiness определяет, как ядро отдает приоритет использованию пространства подкачки по сравнению с физической памятью. Вы можете установить значение подкачки от 0 до 100 с помощью следующей команды: sudo sysctl vm.swappiness=value, где значение — желаемое значение подкачки.
  5. Мониторинг использования подкачки: вы можете отслеживать использование подкачки с помощью команды free, которая показывает общий объем пространства подкачки, использованный и свободный подкачки.

В заключение, пространством подкачки можно управлять, настраивая его во время установки, изменяя его после установки, включая и отключая его, устанавливая значение подкачки и отслеживая его использование.

Пример использования ssh туннеля (tunnel) для sql запроса на python

Более развернуто задача звучит так: есть удаленных сервер с доступом по ssh. на сервере запущена БД. База данных настроена по безопасности так, что запросы можно делать только с локальной машины localhost. Требуется выполнить запрос к базе данных программно. Конечно можно вручную подцепиться к серверу а потом к БД, и выполнить sql query. Можно также залить какой-то скрипт на сервер и запросы пропускать через него. Но как же выполнить SQL запрос через ssh удаленно с рабочей машины? Для этого мы воспользуемся построением SSH туннеля.
Для примера возьмем БД mysql, однако это может быть и postgres и другая БД.
Также важной особенностью будет и то, что доступ по ssh осуществляется через публичный ключ. Вариант авторизации только по логину и паролю также рассмотрим, и добавим в код.
Ключом к решению задачи является библиотека paramiko. В стандартные набор она не входит, поэтому для установки выполняем pip install paramiko. Данная библиотека содержит обширный набор функций для работы по ssh как с серверной так и с клиентской стороны.

import mysql.connector
import paramiko

# параметры соединения по SSH
ssh_username = "ssh_username"
ssh_password = "ssh_password"
ssh_host = "ssh_host"
ssh_port = 22

# параметры соединения по SSH при использовании авторизации по ключу
ssh_username = "ssh_username"
ssh_key_path = "/path/to/ssh/key"
ssh_key_password = "ssh_key_password"
ssh_host = "ssh_host"
ssh_port = 22

# Параметры соединения с БД MySQL
mysql_username = "mysql_username"
mysql_password = "mysql_password"
mysql_host = "127.0.0.1"
mysql_port = 3306
mysql_database = "database_name"

# Загружаем SSH ключ
private_key = paramiko.RSAKey.from_private_key_file(ssh_key_path, password=ssh_key_password)

# Открываем SSH Tunnel
transport = paramiko.Transport((ssh_host, ssh_port))
transport.connect(username=ssh_username, password=ssh_password)
# вместо предыдущей строки, для соединения по ключу, используем параметр pkey
transport.connect(username=ssh_username, pkey=private_key)

local_bind_address = (mysql_host, mysql_port)
transport.start_client()

# Соединяемся с MySQL через SSH Tunnel
cnx = mysql.connector.connect(user=mysql_username, password=mysql_password,
                              host=mysql_host, port=mysql_port,
                              database=mysql_database,
                              transport=transport)

# Делаем селект запрос к базе данных
cursor = cnx.cursor()
query = "SELECT * FROM table_name"
cursor.execute(query)
for row in cursor:
    print(row)

# Закрываем соединение с Базой данных
cursor.close()
cnx.close()

# Закрываем ssh тоннель
transport.close()

Библиотека Python Paramiko используется для безопасных (SSH) подключений к удаленным машинам. Некоторые распространенные варианты использования Paramiko включают в себя:

* Управление ключами SSH
* Передача файлов по SFTP
* Удаленное выполнение команды
* Port forwarding
* Удаленный запуск скриптов

Мягкая остановка скрипта на python по Ctrl+C

Под мягкой или безопасной остановкой скрипта понимается такой процесс прерывания, при котором программа «узнает» что её останавливают и запускает часть кода необходимую для корректного завершения программы. Часто такую остановку называют Graceful Exit или Graceful Kill.
Такое поведение может быть полезно например в случае, когда идёт какой-нибудь расчет и требуется приостановить выполнение с возможностью в дальнейшем запустить продолжение из текущей точки останова. Тогда после поступления сигнала на мягкую остановку данные из оперативной памяти не теряются, а записываются на жесткий диск.
Однако более частое применение — это мягкая остановка цикла, когда необходимо перед выходом из программы довести вычисления до логического конца, т.е. полностью закончить текущую итерацию.

Пример Graceful остановки скрипта в цикле:

 import signal
 import time

 class GracefulKiller:
   kill_now = False
   def __init__(self):
     signal.signal(signal.SIGINT, self.exit_gracefully)
     signal.signal(signal.SIGTERM, self.exit_gracefully)

   def exit_gracefully(self,signum, frame):
     self.kill_now = True
 
 # инициализируем "улавливателя" сигналов
 killer = GracefulKiller()

 # начинаем вычисления, здесь также может быть альтернативный цикл
 while True:
   time.sleep(1)
   print("doing something in a loop ...")
   if killer.kill_now:
     break

 print "Безопасная остановка успешно заверешена!"

Здесь мы используем два типа сигнала, SIGTERM и SIGINT позволяющие охватить пожалуй все случаи мягкого прерывания.
SIGTERM это основной сигнал, который подает операционная система linux для остановки программы. В отличии от SIGKILL сигнал может быть заблокирован, обработан или же проигнорирован. По умолчанию утилита kill отправляет именно сигнал SIGTERM. Для знакомых с программой htop, попробуйте нажать F9 (kill) и по умолчанию будет выбран именно сигнал -15 SIGTERM, так что это пожалуй самый распространенный способ сказать программе «по доброму» — останавливайся.

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

Вообще разных сигналов гораздо больше чем SIGTERM и SIGINT, подразделются они на 3 основных класса. Пользовательские (отправляемые пользователем), системные (от ядра системы) и как бы их назвать «программные» (отправляемые другими программами). Возможно для вашей задачи надо будет отлавливать ещё что-то, но 99.9% должно хватить скрипта сверху :)

Предотвращение запуска второй копии процесса в cron с помощью утилиты flock

Столкнулся значит я со следующей задачей: необходимо чтобы скрипт всё время был запущенным, но вот досада — иногда он падает, причем каждый раз по каким-то новым необъяснимым причинам. Суть такова, что единичные падения совсем не критичны, главное чтобы 99% времени он был запущен. Для контроля, что скрипт запущен решено использовать cron. Но cron будет плодить бесконечное количество клонов скрипта каждую минуту, а нужен только один экземпляр.
Как же предотвратить дублирование процесса и быть уверенным, что запуск скрипта будет осуществлен только после падения предыдущего запуска? Беглый поиск показал, что с этим справится утилита flock, позволяющая блокировать запуск новых экземпляров скрипта.

Пример того, как выглядит запуск скрипта на python при использовании flock в задачах по расписанию cron:

* * * * * /usr/bin/flock -n /tmp/my.lockfile /usr/bin/python3 /path/to/script.py

при вызове надо будет указать файл блокировки, должен находится в директории с правами на запись. В некоторых руководствах советуют писать в папку /var/run но я с этим не заморачивался.

После завершений программы (или в моём случае при падении), блокировка, выставленная через файл my.lockfile, снимется и процедура может быть запущена вновь.
Параметр -n означает что flock сразу останавливает своё выполнение, если процесс блокирован.
Для Вашей настройки можете посмотреть также параметр -w (alias —wait —timeout) устанавливающий количество секунд ожидания разблокировки. Если за указанное время процесс не разблокируется, то попытка будет остановлена (exit code 1).

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

Пример запуска Scrapy отдельным скриптом, без создания проекта

Scrapy представяет собой серъёзный фреймворк для обхода сайтов и извлечения из них структурированной информации. Отличается большим диапазоном решаемых задач (сбор данных, мониторинг, автоматическое тестирование, итд) а также отличной скоростью (благодаря асинхронным вызовам). Использование scrapy подразумевает создание отдельного проекта, со своей заданной структурой и настройкой логики/методов обхода в отдельных python классах scrapy.Spider .
Но что если мы хотим использовать все плюшки большого фремворка, не создавая непосредственно проект? Скажем надо сделать маленькую подзадачу в другом проекте: залогиниться, перейти на заданную страницу и вытащить что-то из html кода?
Столкнувшись с данной задачей мне не удалось быстро найти решение на просторах интернета, поэтому опишу здесь что у меня получилось:

# Устанавливаем библиотеку
pip3 install scrapy

Сценарий следующий:
1. Заходим на стартовую страницу сайта (SCRAPY_START_URL), на которой находится форма входа
2. После успешного залогинивания переходим на вторую страницу с данными SCRAPY_SECOND_URL
3. В функции action из html кода получаем необходимые данные, путём указания пути через аналоги css-селекторов
* Для вызова всего кода используем функцию get_data_by_id, которая по data_id конкретизирует вторую страницу.
* Во время исполнения будет создаваться временный файл items.json, который будет перезаписываться после каждого вызова (он сейчас специально удаляется, если не удалять, то новые результаты будут записываться в конец файла). items.json создается в Scrapy по умолчанию, и как-то без него скорее всего нельзя, но можно задать формат, если вдруг json вам не по душе.
* пример ниже:

import json
import os
import scrapy
from scrapy.crawler import CrawlerProcess, CrawlerRunner
from twisted.internet import reactor
from multiprocessing import Process, Queue


SCRAPY_START_URL = 'https://example.com/'
SCRAPY_USER = 'user_name'
SCRAPY_PASS = 'some_pass'
SCRAPY_SECOND_URL = 'https://example.com/data/'


class CPSpider(scrapy.Spider):
    name = "crawler_name_here"
    start_urls = [SCRAPY_START_URL]
    second_url = None

    def parse(self, response):
        yield scrapy.FormRequest.from_response(
            response,
            formxpath='//form[@id="loginForm"]',
            formdata={
                'auth_signin[username]': SCRAPY_USER,
                'auth_signin[password]': SCRAPY_PASS,
                'Action': '1',
            },
            callback=self.after_login)

    # запускается после успешного логина
    # переходим на странцу, откуда надо стянуть данные
    def after_login(self, response):
        yield scrapy.Request(
            url=CPSpider.second_url,
            callback=self.action)

    # вытаскиваем данные из html кода с помощью xpath
    def action(self, response):
        final_result_txt = response.xpath('//span[@class="data_display"]/b/text()').extract_first(default='some_default')
        print(final_result_txt)

        # создадим словарь и запишем полученный резальтат
        item = dict()
        item['final_result'] = final_result_txt
        return item


def get_data_by_id(data_id):
    if os.path.isfile('items.json'):
        os.remove('items.json')

    CPSpider.reseller_url = SCRAPY_SECOND_URL + str(data_id)

    # а вот здесь хитрая обертка
    # благодаря таким фокусам можно не запускать основное приложение
    def run_spider(spider):
        def f(q):
            try:
                runner = CrawlerRunner(settings={
                    # установим уровень ошибок выше дефолта
                    # чтобы не сыпалось много отладочного вывода
                    # если надо отдебажть, то ставим DEBUG
                    'LOG_LEVEL': 'ERROR',
                    'FEED_FORMAT': 'json',
                    'FEED_URI': 'items.json'
                })
                deferred = runner.crawl(spider)
                deferred.addBoth(lambda _: reactor.stop())
                reactor.run()
                q.put(None)
            except Exception as e:
                q.put(e)

        q = Queue()
        p = Process(target=f, args=(q,))
        p.start()
        result = q.get()
        p.join()

        if result is not None:
            raise result

    try:
        run_spider(CPSpider)
        # считываем результат, полученнный после работы scrapy
        with open('items.json') as json_file:
            res = json.load(json_file)
        if len(res):
            return res[0]['final_result']
    except BaseException as e:
        return 'error'

Сравнение 3ёх фреймворков для машинного обучения: Keras, TensorFlow и PyTorch

Keras, TensorFlow и PyTorch входят в тройку наиболее используемых DL фреймворков. Эти библиотеки используют и как продвинутые дата-инженеры, так и новички в области глубокого обучения. Чтобы облегчить Вам выбор библиотеки, здесь приведем сравнение между троицей Keras, TensorFlow и PyTorch.

Keras — это открытая библиотека для построения нейронных сетей. Написана на Python. По сути это обертка поверх TensorFlow. Идеалогически построена для быстрого прототипирования глубоких нейронных сетей.

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

Pytorch — основное отличие заключается в том, что разработана была в Facebook AI, поэтому имеет несколько другой интерфейс. Однако многим разработчикам нравится именно библиотека, хоть она и слабее распространена

Параметры сравнения библиотек
* простота использования
* скорость работы
* архитектура
* легкость отладки
* наличие подготовленных наборов данных
* популярность

1.простота использования
Keras — обладает наиболее высокоуровневым API, позволяет работать поверх TensorFlow, CNTK и Theano. Завоевал популярность благодаря синтаксической простоте, способствуя быстрой разработке.
TensorFlow — является одноверменно и высокоуровневой библиотекой так и позволяет вносить изменения в нейронную сейть на весьма низком уровне.
Pytorch — предоставляет низкоуровневый API, ориентированный непосредственно на работу с массивами матриц. Он приобрел огромный интерес в академической среде и в научных исследованиях по глубокому обучению.

2. Скорость/производительность
Производительность в Keras немного ниже чем в Tensorflow и PyTorch, которые обеспечивают
примерно одинаковую скорость, подходящую для серъёзных нагруженных проектов.

3. архитектура
В Keras реализована относительно простая архитектуру, является более читабельной и краткой. С этой стороны, Tensorflow более сложен, хоть Keras и базируется на нем. PyTorch же имеет сложную архитектуру и читаемость кода на нем меньше в сравнении с Keras

4. легкость отладки
В Keras обычно очень редко возникает необходимость отладки простых сетей. Большой набор примеров и армия разработчиков на Keras позволит быстро локализовать ошибку.
В Tensorflow отладка наверняка займет больше времени.
Pytorch обладает хорошим набором инструментов для отладки по сравнению с двумя другими.

5. Работа с датасетами
Keras обычно используется для небольших наборов данных, поскольку он несколько медленнее. А вот TensorFlow и PyTorch используются для высокопроизводительных моделей и большие датасеты не должны вызвать затруднения.

6. популярность
В связи с растущим спросом на технологию глубокого машинного обучения эти три фреймворка приобрели огромную популярность среди python разработчиков. Согласно гугл-трендам, Keras возглавляет список и становится самым популярным, за ним следуют TensorFlow и PyTorch с некоторым отставанием.

Подведем итоги
Для этого рассмотрим ситуации, при которых предпочтительнее будет выбрать ту или другую библиотеку.

Keras наиболее подходит когда для Вас важны:
* Быстрое прототипирование
* При малом наборе данных
* Поддержка сообщества

TensorFlow наиболее подходит когда для Вас важны:
* Работа с большими наборами данных
* Высокая производительность
* функциональность

PyTorch наиболее подходит когда для Вас важны:
* гибкость
* малая продолжительность обучения библиотеки
* быстрая отладка

Пример нахождения косинусного расстония между текстами в sklearn

Косинусное расстояние — это распространенная метрика поиска схожих объектов, часто в разряженном пространстве. Поэтому косинусное расстоние часто применяют для сравнения текстов. Ниже приведу код для поиска наиболее похожего текста с помощью встроенных функций sklearn.
Пусть у нас есть большое количество текстов, предобработаем тексты с помощью tf-idf, после этого возьмем новый текст и найдем 3 самых похожих.

import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel

# зададим массив текстов
some_texts = [
   'текст номер один',
   'текст следующий под номером два',
   'третий набор слов',
   'что-то ещё для полноценного дата-сета',
   'пример текстового документа',
]
df = pd.DataFrame({'texts': some_texts})

# А к данному тексту будем искать наиболее похожий из заданного выше набора
find_nearest_to = "текст номер три"

# формирование весов tf-idf
tfidf = TfidfVectorizer()
mx_tf = tfidf.fit_transform(some_texts)
new_entry = tfidf.transform([find_nearest_to])

# расчет косинусного расстояния
cosine_similarities = linear_kernel(new_entry, mx_tf).flatten()

# запишем все попарные результаты сравнений
df['cos_similarities'] = cosine_similarities
# и отсортируем по убыванию (т.к. cos(0)=1)
df = df.sort_values(by=['cos_similarities'], ascending=[0])

# Выведем 3 самых близких результата
for index, row in df[0:3].iterrows():
    print(row['texts'], row['cos_similarities'])
# output:
# текст номер один 0.7891589913464455
# текст следующий под номером два 0.23490553492076713
# третий набор слов 0.0

Заметим, что в данном примере мы не использовали cosine_similarity, а почему-то взяли linear_kernel. Дело в том, что когда вектора нормализованы (а они нормализованы после TfidfVectorizer) результат будет одинаковый, однако linear_kernel требует меньше вычислительной мощности и вычислится быстрее. Особенно это будет хорошо заметно на больших данных. Если же вектора не нормализованы, то правильно использовать sklearn.metrics.pairwise.cosine_similarity .

Пример также можно рассматривать как рекомендательную систему на большом корпусе каких-то объектов, работает быстро и хорошо подходит для бейзлайна. В случае с объектами не текстовыми, рекомендуется также попробовать использовать CountVectorizer, вместо TfidfVectorizer.

Пример классификации текстов на python с помощью fasttext

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

Здесь мы будем использовать напрямую библиотеку fasttext, однако если Вы продвинутый инженер, то возможно лучше взять обертку gensim, т.к. мне показалось что с ней работа будет более гибкой.

Для установки воспользуемся командой:

pip3 install fasttext

Непосредственно пример:

import pandas as pd
import fasttext


# зададим набор текстов и меток классов
some_texts = [
   'текст номер один',
   'текст следующий под номером два',
   'третий набор слов',
   'что-то ещё для полноценного дата-сета',
   'пример текстового документа',
]
some_labels = [1, 1, 0, 0, 1]

# А теперь одно из разачарований имплементации именно этой библиотеки:
# Для обучения придется сделать файл, где целевой класс должен начинаться с __label__
df_train = pd.DataFrame({'txt': some_texts, 'target': some_labels})
df_train['target'] = df_train['target'].apply(lambda x: '__label__' + str(x))
df_train[['target', 'txt']].to_csv('train_data.txt', header=False, index=False, sep="\t")

# обучаем на 20 эпохах, полный набор гиперпараметров можно взглянуть на официальном сайте fasttext.cc
model = fasttext.train_supervised(input='train_data.txt', epoch=20)

# указываем чтобы отдавало вероятности обоих классов, по умолчанию k=1
p = model.predict('текст номер четыре', k=2)

print(p)
# (('__label__1', '__label__0'), array([0.5010367 , 0.49898329]))

Второе разачарование заключается в том, что в итоговом предсказании выдаётся сначала самый вероятный класс, и если требуется получить вероятность по конкретному классу, то нужно сначала узнать индекс нужного класса и только после этого по индексу взять вероятность. Для 2ух классов можно сделать примерно так:

    if p[0][0] == '__label__0':
        real_probability = p[1][1]
    else:
        real_probability = p[1][0]

В целом данный пример предлагается использовать для быстрого получения результата в задаче классификации текстов. Я попробовал код выше на соревновании mlbootcamp . ru по классификации экспертных вопросов и он показал результат из коробки лучше чем tf-idf: 0.73 fasttext против 0.68 tf-idf + lgbm

Группировка (groupby) в pandas c сохранением количества строк, генерация агрегатных фич.

Пусть у нас есть датафрейм и нам надо сгруппировать его по колонке «A»

import pandas as pd
import numpy as np


df = pd.DataFrame()
df['A'] = [1, 1, 1, 1, 1, 2, 2, 3, 3, 3]
df['v'] = [3, 5, 7, 10, 20, 11, 1, 3, 8, 10]

print(df.head(10))
# распечатаем датафрейм для наглядности
   A   v
0  1   3
1  1   5
2  1   7
3  1  10
4  1  20
5  2  11
6  2   1
7  3   3
8  3   8
9  3  10

Для группировка выберем функцию среднего и после применения операции группировки в DataFrame остается всего 2 колонки. Пример:

res = df.groupby(by=['A'], as_index=False)['v'].mean()
print(res.head())
# результат
   A  v
0  1  9
1  2  6
2  3  7

Как сделать так, чтобы просто добавить результат агрегации в новую колонку? Для этого воспользуемся функцией transform

df['col_mean'] = df.groupby(by=['A'], as_index=False)['v'].transform(lambda s: np.mean(s.values))
print(df.head(10))
# получаем результат агрегации в новой колонке
   A   v  col_mean
0  1   3       9.0
1  1   5       9.0
2  1   7       9.0
3  1  10       9.0
4  1  20       9.0
5  2  11       6.0
6  2   1       6.0
7  3   3       7.0
8  3   8       7.0
9  3  10       7.0

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

import pandas as pd
import numpy as np
import scipy
from scipy import stats


def make_agg(df_in, group_col_name, stat_col_name):
    df = df_in.copy()
    # то же самое что и count, количество
    df['col_count'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: s.values.shape[0])
    # среднее значение по сгруппированному столбцу
    df['col_mean'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: np.mean(s.values))
    # медиана по сгруппированному столбцу
    df['col_median'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: np.median(s.values))
    # сумма по сгруппированному столбцу
    df['col_sum'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: np.sum(s.values))
    # минимум и максимум по сгруппированному столбцу
    df['col_max'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: np.max(s.values))
    df['col_min'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: np.min(s.values))
    # стандартное отклонение по сгруппированному столбцу
    df['col_std'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: np.std(s.values))
    # квантили в 20 и 80 % по сгруппированному столбцу
    df['col_q20'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: np.quantile(s.values, q=0.2))
    df['col_q80'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: np.quantile(s.values, q=0.8))
    # skew
    df['col_skew'] = df.groupby(by=[group_col_name], as_index=False)[stat_col_name].transform(lambda s: scipy.stats.skew(s))

    return df

res = make_agg(df, 'A', 'v')
print(res.head(10))

Как видите в transform можно написать любую свою lambda функцию. Только помните, что переменная придет типом Series, и для того чтобы случайно не запутаться в индексах, рекомендую добавлять values, тем самым переходя к стандартному numpy массиву. Также считать агрегатные статистики лучше сразу по объединенному train+test датасету, а не по отдельности для train и test. Так как объединенный датасет будет более показательным.

Две функции повышения точности классификатора с помощью target encoding / mean encoding

Раз Вы смогли нагуглить эту статью, значит скорее всего знаете что такое target encoding, а тажке ознакомлены со всеми прелестями этого мощного метода и знаете все опасности переобучения, которые могут возникнуть при неправильном использовании подхода.
Поэтому здесь я приведу лишь две функции, которые можно быстро использовать для Ваших задач.
Если же не знакомы c mean encoding, то вкратце так:
На основе знаний о target, и его распределения по какой-нибудь категориальной фичи можно добавить новую переменную, показывающую характеристику встречаемости чисто внутри какой-то категории. Для примера пусть есть датасет с разными характеристиками автомобиля, необходимо предсказать город, где был куплен автомобиль. Можно подсчитать среднее кол-во каждой марки в каждом городе и добавить это как дополнительную фичу. Так скажем для тойоты это число будет больше во Владивостоке, чем для Москвы и наоборот, какой-нибудь мерседес явно будет более встречаемый в Москве, нежели рядом с Японией. Как видим, разделяемость по новой фиче будет очень хорошая. Но в этом и есть большой риск переобучения (например какая-нибудь редкая марка, которая в датасете встретится всего пару раз и в одном городе) поэтому напрямую считать среднее не рекомендуется, а правильнее будет использовать регуляризации. Ниже приведены две функции для корректного использования mean-encoding:

Функция 1: CV loop регуляризация
Достаточно интуитивный и устойчивый метод для mean encoding. Данные разбиваются на фолды и значение для каждого фолда вычисляется на основе оставшихся фолдов. Таким образом не будет переобучения на текущем куске данных, т.к. вычисляемое среднее значение будет браться на остальном подмножестве данных. Обычно 4-5 фолдов достаточно.

# Для начала создадим дата фрейм с категориальной фичей
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold


cities = ['C1', 'C1', 'C1', 'C2', 'C2', 'C3', 'C3', 'C3', 'C3', 'C3']
target = [1, 1, 0, 1, 0, 1, 1, 0, 0, 0]

df = pd.DataFrame()
df['cities'] = cities
df['target'] = target

def make_mean_encoding(df_tr, target_col_name, feature_col_name):
    y_tr = df_tr[target_col_name].values
    skf = StratifiedKFold(n_splits=2, shuffle=True, random_state=123)
    train_new = df_tr.copy()

    global_mean = df_tr[target_col_name].mean()
    train_new[feature_col_name + '_mean_target'] = global_mean

    for tr_ind, val_ind in skf.split(y_tr, y_tr):
        X_tr, X_val = df_tr.iloc[tr_ind], df_tr.iloc[val_ind]
        for col in [feature_col_name]:
            means = X_val[col].map(X_tr.groupby(col)[target_col_name].mean())
            X_val[col + '_mean_target'] = means
        train_new.iloc[val_ind] = X_val

    # fill nan
    train_new.fillna(global_mean, inplace=True)

    return train_new

df_encoded = make_mean_encoding(df, 'target', 'cities')
print(df_encoded.head(10))

Функция 2: Регуляризация на основе размытия
Суть данной регуляризации в том, что мы доверяем большим категориям, с большим кол-вом значений и устанавливаем коэффициент недоверия к небольшим, слабопредставленным категориям. По сути говоря коэффициент alpha это размер группы, начиная с которой мы доверяем среднему значению

def make_mean_encoding_smooth(df_tr, target_col_name, feature_col_name):
    train_new = df_tr.copy()
    global_mean = df_tr['target'].mean()
    encod_type = df_tr.groupby(by=[feature_col_name], as_index=False)[target_col_name].transform(np.mean).values
    nrows = df_tr.groupby(by=[feature_col_name], as_index=False)[target_col_name].transform(len).values
    alpha = 4
    train_new[feature_col_name + '_mean_target'] = (encod_type * nrows + global_mean * alpha) / (nrows + alpha)
    return train_new

df_encoded = make_mean_encoding_smooth(df, 'target', 'cities')
print(df_encoded.head(10))

Некоторые библиотеки градиентного бустинга из коробки умеют делать mean-encoding, например catboost. Делает котяра эту вещь отменно, достаточно просто указать категориальные фичи, и мне даже когда-то не хотелось лезть в эту тему, но в один момент надо было работать с очень большим датасетом, и оказалась что эта фича библиотеки ужасно прожорлива на используемую память, а если использовать ещё и обучение на видеокарте, то терабайтов оперативной памяти не хватит. Поэтому иногда полезно самим прикрутить фичи на основе mean-енкодинга, кроме того, за основу не обязательно брать таргет, можно поэкспериментировать и с другими величинами из датасета.