Заметки Дата Саентиста: маленькие утилиты — большая польза
Чаще всего в работе датасаентиста мне приходится перегонять данные из одного представления в другое, агрегировать, приводить к одинаковой гранулярности и чистить данные, загружать, выгружать, анализировать, форматировать и присылать результаты (которые в общем-то тоже данные в каком-то виде). С данными всегда что-то не так и их нужно шустро гонять туда и обратно — больше всего в этом мне помогают классические юниксовые утилиты и небольшие, но гордые тулзы: вот о них-то мы сегодня и поговорим.
И сегодня будет подборка с примерами и ситуациями, в которых мне приходится их использовать. Все описанное здесь и ниже — это настоящий субъективный опыт и конечно же он у всех разный, но возможно кому-то он будет полезен.
Tools — learn the tools — все написанное субъективно и основано исключительно на личном опыте: помогло мне может быть поможет и вам.
Zsh и oh-my-zsh — после всех этих лет в Баше!
Как сейчас помню, мне было 17 лет и я установил линукс. Терминал и Баш. И как-то всегда баш был частью процесса и олицетворял для меня собственно работу в терминале. Спустя 12 лет после окончания PhD я попал в компанию, где был вводный документ и мне впервые в руки попался мак и я решил ему следовать.
И о чудо! Удобный переход по папкам, человеческое автодополнение, индикатор git, темы, плагины, поддержка виртуальной среды для python и тд — сижу теперь в терминале и не нарадуюсь!
Ставим zsh, как вы обычно ставите все и переходим к oh-my-zsh (по сути — это народная сборка рецептов, которые работают из коробки и добавили поддержку плагинов, тем и тд). Взять можно тут. А также можно поставить тему (ну например). Вот тут неплохое демо возможностей. Взято из вот этой статьи.
Pipelines
Одна из самых прекрасных конструкций терминала — это pipeline. Упрощенно говоря, он позволяет соединять выходы одной команды с входами другой, пример простого применения, который буквально взят из одной задачи, которой я занимался два дня назад.
Нужно было смоделировать задачу на одном языке для решения комбинаторных задач, запускалось все из терминала и выдавалось в абсолютно нечитаемом виде из текста, поставив простой значок | — соединили входы выходы и сделали поддержку форматирования:
| python.py format.py
Более интересная и каждодневная задача — оценить какой-нибудь параметр или характеристику по отгруженным данным, как правило — это ряд быстрых проверок, что нужные величины где-то на сервере с данными ведут себя хорошо — например мы хотим понять, что у нас с парсером и смотрим сколько во всех json файлах собрано уникальных групп — этот параметр должен естественно адекватно расти во времени:
cat data/*groups* | jq .group | uniq | wc -l
Мы подробнее поговорим о каждом из них, но общая идея уже понятна:
- cat — (сокр. от concatenate) печатает содержимое файлов со словом «group» в названии из папки data/
- jq — выдирает из json поле «group»
- uniq — оставляет только уникальные группы
- wc — с ключом -l считает строчки, т.е количество групп
И сейчас мы подробнее посмотрим на wc.
WC — маленькая, но гордая утилитка для подсчета строк, слов и тд.
wc — умеет быстро считать слова, строчки, буквы, байты и максимальную длину строчки, и все с помощью простых ключей:
—bytes
—chars
—words
—lines
—max-line-length
Кажется, что это тривиально, но оказывается невероятно часто нужно и удобно.
Повседневное использование, быстро оценим сколько у нас каких данных собрано (тут одна строка одна запись):
Подробнее тут.
Ack/grep
Про них написаны тысячи мануалов и статей, но не могу не упомянуть — выдирают текст регулярками и своим языком запросов по соответствию образцу. В целом мне кажется ack более дружелюбным и простым в использовании из коробки, поэтому будет он тут:
Пример: быстренько находим вхождение слова (ключ »-w») ga2m (тип модели), без учета регистра (ключ -i) в файлах исходниках питона:
JQ — парсим json в командной строке
Документация.
JQ — это прямо-таки grep/ack для json (хотя и с оттенком sed и awk — о последнем далее) — по сути простой парсер json и json line в командной строке, но иногда бывает чрезвычайно удобным — как-то пришлось парсить архив wikidata в формате bz2 он весит порядка 100ГБ и где-то 0.5TB uncompressed.
Из него нужно было выдрать соответствие между несколькими полями, что получилось сделать очень просто на машине практически без нагрузки на CPU и память, вот собственно та самая команда, которую я использовал:
bzcat data/latest-all.json.bz2 | jq —stream 'select((.[0][1] == "sitelinks" and (.[0][2]=="enwiki" or .[0][2] =="ruwiki") and .[0][3] =="title") or .[0][1] == "id")' | python3 scripts/post_process.py "output.csv"
Это был по сути весь пайплайн, который создавал нужный mapping, как мы видим все работало в режиме потока:
- bzcat читал часть архива и отдавал jq
- jq с ключом —stream сразу выдавал результат и передавал его постпроцессору (так же как и с самым первым примером) на питоне
- внутри постпроцессор — это простая машина состояний
Итого сложный пайплайн работающий в режиме потока на больших данных (0.5TB), без существенных ресурсов и сделан из простого пайплайна и пары тулзов. Определенно рекомендую глянуть на досуге.
fzf — fuzzy finder
Удобнейшая вещь (особенно внутри вима): быстро ищет по файлам, что удобно на большом проекте — особенно, когда их у вас несколько. Как правило нужно для быстрого поиска файлов по определенному слову в большом проекте: у меня происходило погружение в новый проект, в который входит несколько крупных репозиториев и в качестве вводного задания мне нужно было добавить одну простую модель в ассортимент доступных в системе и мне нужно было быстро находить свои файлы по ключевому слову ga2m и работать по аналогии с другими «блоками кода» — быстро редактировать то одно, то другое — тут fzf очень хорошо приходит на помощь:
Ссылка на репозиторий.
AWK
Название происходит от первых букв создателей Aho, Weinberger и Kernighan: по сути скриптовый язык обработки текстово-табличных данных — он приминяет шаблоны трансформации к каждой строке файла
Как правило идеально подходит для быстрых разовых трансформаций, например, у нас были собранный руками датасет в виде tsv, а процессор принимал на вход jsonl причем ожидал доп поле «theme», которого не было в исходном файле (нужного для некоторых вещей, которые были не критичны для текущих подсчетов) — итого, был написан простой однострочник:
cat groups.tsv | awk '{ printf "{\"group\": \"%s\", \"theme\": \"manual\" }\n", $1 }' > group.jsonl
По сути он брал файлик и каждую строчку заворачивал json с нужными полями.
Ссылка с tutorial.
wget — универсальный command line downloader
Регулярно скрипты и пайплайн должны что-то откуда-то подтянуть и качнуть — и wget не подводит: умеет докачивать, авторизовываться, proxies, cookies да еще и помимо http (s) умеет в ftp.
Швейцарский нож в скачивании.
HSTR — поиск по истории команд, с человеческим лицом
Сommand history: hstr
Регулярно мне приходится искать что-то в истории команд:
- «Это уже приходилось делать»
- «А с каким ключам запускается Х?»
- «А вот этот кусок можно и переиспользовать»
Поэтому мне довольно критично иметь хороший и удобный поиск по истории команд, пока hstr полностью со своей задачей справляется:
Полезное, но не вошедшее
В финале я бы упомянул полезными —, но тянущими на отдельную статью темы — полезно глянуть:
- Связку ssh + tmux + vim (neo, plugins, etc)
- Базовые знания command line git + git hooks
- Data pipeline construction make/just
- Python virtual environments
- Docker