Pull to refresh
8
0

Прикладной математик, разработчик R

Send message

Да, подход с присваиванием подмножеству строк в данном случае более удобен с точки зрения количества изменений столбцов, но менее гибкий с точки зрения универсальности правила обновления. В частности, он не работает, если правило обновления столбцов зависит от всех строк (хотя и применить его нужно только для подмножества). Например, если вместо 2 нужно взять минимум столбца a по всему датафрейму. Здесь исходный подход 'data.table' тоже оказывается не рабочим.


Кстати, для создания многих столбцов по одному правилу пока что есть mutate_at():


library(tidyverse)
dat <- tibble(a = 1:4, b = 1:4, c = 1:4, d = 1:4)
dat %>%
  mutate_at(vars(b, c, d), ~ifelse(a > 1, 2*., .))
#> # A tibble: 4 x 4
#>       a     b     c     d
#>   <int> <dbl> <dbl> <dbl>
#> 1     1     1     1     1
#> 2     2     4     4     4
#> 3     3     6     6     6
#> 4     4     8     8     8

Преимуществом здесь считается более "выразительный" код, который "читается как обычный текст": "взять dat, произвести обновление b, c, d по следующему правилу". Но это больше обусловлено привычкой читать код.
И да, каждый раз фильтрация будет заново вычисляться. Это минус, согласен. Его, конечно, можно обойти, но это если сильно важна производительность.

Аналогичный результат в 'dplyr' можно получить используя ifelse():


dat %>%
  mutate(b = ifelse(a > 1, b * 2, b))
#> # A tibble: 4 x 2
#>       a     b
#>   <int> <dbl>
#> 1     1     1
#> 2     2     4
#> 3     3     6
#> 4     4     8

Как по мне, такой подход более явно демонстрирует алгоритм вычисления b, чем с 'data.table'.

Великолепная статья! Пожалуй, лучший пример нейтрального сравнения разных подходов.


Сам после 7 лет использования R решил изучить Python (базовый, numpy, pandas, вот это вот всё). Для себя выделил три самых "сложных" отличия (с точки зрения "запоминания" до уровня комфортного использования):


  • Изменение объекта напрямую вместо создания копии. В R такое практически не практикуется ('data.table' одно из исключений), а вот в Python встречается часто. Правда, pandas тоже предпочитает создавать копии, но следует быть аккуратным. Больше всего проблем с этим у меня при работе с датафреймами внутри функции:

def foo(df):
    df["z"] = 100
    return None

data = pd.DataFrame({"a": [1]})
foo(data)
# В `data` добавлен столбец 'z'. В аналогичном коде R `data` не изменяется
data
>>>    a    z
>>> 0  1  100

  • Фильтрация по индексу элемента. Речь не столько о том, что индексы в Python начинаются с 0, а в R с 1. Концептуально сложнее для меня почему-то две другие вещи. Во-первых, трактовка отрицательных индексов: x[-1] в R это всё кроме первого элемента, а в Python — один последний элемент. Во-вторых, конструкция x[i:j] не включает элемент с индексом j в Python, а в R включает.
  • Наличие строковых и столбцовых индексов в DataFrame. Для меня это самое важное и большое концептуальное отличие tidyverse и pandas. В tidyverse настаивается на том, чтобы все данные хранились в ячейках таблицы. В pandas же часто правильно выбранный индекс (обычно строковый) упрощает работу с данными. Примером здесь служит выравнивание по индексу при использовании различных операций на паре датафреймов.

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

Спасибо, полезная статья по демонстрации практических возможностей R.


А ещё меня приятно удивило качество кода (оформление и использованные подходы). Пара вещей, которые бросились в глаза:


  • Достаточно много мест, читаемость которых может улучшить использование pipe-оператора %>%. Например, вот эти строки можно записать как одну цепочку операций без ненужных переприсваиваний.
  • Вы подгружаете весь пакет tidyverse, а используете только функции из stringr. Можно было бы использовать побольше tidyverse функций, что тоже упростило бы читаемость. Например, вместо этой и этой строк можно было использовать map_dfr() из пакета purrr (и опять же можно было не использовать "лишнюю" переменную table).
  • Использование явных методов обобщённых (generic) функций обоснованно только в редких случаях (например, когда очень важны несколько десятков микросекунд). Поэтому вместо rbind.data.frame здесь лучше использовать просто rbind. Или вообще вместо всей команды функцию bind_rows().
Согласен, но вопрос в том, являются ли эти базовые типы классами или нет.

Как всегда, всё зависит от того, когда X считать "объектом класса Y" (в S3 ООП). На практике это означает, что "Y" присутствует в результате вызова class(X). В документации этой функции говорится, что это значение атрибута "class", но если его нет, то тогда используется его "неявный класс" (что видится, как результат особого if-else в коде функции).


Моё понимание такое, что "numeric vector", "logical vector" и компания являются "базовыми типами", которые могут быть использованы при S3 ООП благодаря некоторым фиксированным модификациям в коде base R. Частично это подтверждается тем, что существует функция oldClass() которая в случае обычных векторов возвращает NULL, что говорит об отсутствии класса.

(в которой всё запутано и противоречиво)

Я бы так не сказал. Безусловно, есть запутанные вещи, которые возникли из-за первоначальной ориентированности на прикладное использование, но это далеко не "всё".


… это прикладной язык для статистики.

R прошёл достаточно большой путь с момента его создания. На нём уже можно делать значительно больше и без особых неудобств.


… такое разделение немного надуманно

Выделение отдельных "векторных типов" можно считать аналогом выделения в других языках программирования отдельных базовых типов для числа, символа, булевой переменной, и др. Только в R это всё уже сразу считается вектором, т.е. одно число — это вектор длины 1, и т.д.

Спасибо, очень хорошая статья для начинающих. Будет на что ссылаться для не англоговорящих.


Парочка замечаний для не начинающих:


  • Вектор — это не класс. Насколько я понимаю, даже не существует такого отдельного типа "вектор", а есть разные "векторные типы": "вектор logical", "вектор integer", и т.д (источник). Более точно будет сказать, что S3 класс — это то, что возвращает функция class(). В наиболее типичных случаях это значение атрибута "class", но не всегда. Для вектора letters она возвращает "character", хотя атрибута "class" у него нет.
  • Хотя для создания собственного класса действительно достаточно создать список (list), это не обязательно. Для этого можно использовать любой объект, кроме NULL. Например, базовые векторы, функции и т.д. Для этого достаточно изменить атрибут "class" (например, как Вы пишете, с помощью вызова class(x) <- "a").
Очень грустная вставка до Отрывка 7.8.4.
Пара вещей, за которые зацепился глаз:
  • замечательного пакета R
    R — это язык, в пакетах для которого реализуются различные статистические методы и не только
  • кадр данных в R представляет собой список
    Здесь под «кадр данных», судя по всему, понимается «data frame». Это, как минимум, лучше было бы перевести как «таблица данных». Есть ещё варианты «матрица данных» и просто оставить без перевода.


Ну а книга сама по себе замечательная. Это был мой первый «серьёзный» учебник по R, который научил многому хорошему по именно языку R. Однако, там достаточно мало рассматриваются дополнительные популярные на сегодняшний день пакеты, которые важные для современного использования (tidyverse, shiny, rmarkdown, и т.д.).
Судя по всему, решений «много», потому что ваша целевая функция F(usd, eur, chf, gbp) инвариантна относительно умножения на скаляр: для любого a>0, F(a*usd, a*eur, a*chf, a*gbp) = F(usd, eur, chf, gbp). Это значит, что если какой-то набор (usd', eur', chf', gbp') минимизирует функцию, то то же можно сказать и про любой набор (a*usd', a*eur', a*chf', a*gbp').
Избежать этого можно положив, например, usd + eur + chf + gbp = 1, но тогда метод оптимизации должен быть другой.
Качественная, исчерпывающая подборка. Хотелось бы поделиться некоторыми субъективными наблюдениями:
  • 50 часов на весь этот материал скорее всего хватит только на поверхностное чтение об азах и на выполнение стандартных написанных примеров. Тут работы-изучения минимум на месяц безвылазного чтения и написания кода.
  • Я бы обучал основам написания функций как можно раньше, т.к. это очень важный навык для «написания качественного и эффективного кода»: понимать, что код хорошо бы разбивать на маленькие понятные функции.
  • Нет ничего про тестирование написанного кода (testthat, covr, и т.д.). Это должно быть важно для «промышленной» разработки.
  • Сюда можно добавить азы создания пакетов, как принятого способа организации и документирования кода.
  • Очень полезная книга для такого рода «быстрого обучения написанию эффективного кода» — Efficient R programming
Вам в комментариях к прошлой статье дважды подсказали, что эти вероятности
можно вычислить явно, т.к. все величины имеют бета-распределения с соответствующими параметрами:

  • Вероятность выиграть игру до n побед при вероятности выигрыша p одного очка равна image (используя регуляризованную неполную бета-функцию).
  • Соответственно, «как меняется вероятность выигрыша игры при изменении вероятности выигрыша очка» имеет вид плотности вероятности бета-распределения с параметрами n и n. И действительно, чем больше n, тем меньше дисперсия распределения.
  • Вероятность выиграть n очков до того, как соперник выиграет m очков, при вероятности выигрыша p одного очка равна image. На своих графиках Вы получили плотности бета-распределений при m = n-1.
Спасибо.
Она больше задумывалась как практическое изучение узкой области с использованием математики, а не статья по математике, поэтому как-то даже и не подумал о добавлении в этот хаб. Но да, возможно, нужно было.
Это не совсем задача о разорении игрока, т.к. там игрок может выиграть только «забрав деньги» у соперника. Она бы подходила, если бы победа в спортивном матче присуждалась когда кто-то из игроков «оторвался» от другого на фиксированное количество очков.

Задача в статье — это вариант Задачи о разделении ставки, достаточно подробно описанная в этой статье (обе ссылки на англоязычные источники).

Но и у этой задачи (набрать image очков до того, как соперник наберёт image очков, при вероятности image выигрыша одного очка) есть интересные аналитические решения:

Я использовал эти формулы для создания модификации рейтинга Эло, который учитывает матчи «до image побед». Вот ссылка на хабр-статью, если вдруг интересно.
Спасибо. Именно они и вдохновили меня прочитать книгу.
По поводу не использования полноты информации. Я исхожу из предположения, что перед игроками стоит задача выиграть матч до n побед. Модель обычного Эло на бинарных (победа/поражение) результатах матча не учитывает параметр n (недоиспользует информацию). Ваш подход неявно пытается это делать, но исходит из предположения, что конкретный счёт имеет значение в оценке силы игроков (переиспользование информации, по моему мнению). При текущем формате проведения соревнований в снукере мне это видится не разумным, т.к. единицей учёта достижений является факт победы в матче, а не его счёт. И проблема состоит как раз в том, чтобы учитывать «весомость» каждого матча, а не «весомость» счёта в каждом матче.

Ни в коем случае не предполагается, что все варианты развития матча равновозможны. У каждого из них есть своя вероятность. Другое дело, что конкретный счёт не учитывается в выводе по причинам из предыдущего абзаца. Использование такой модели всё равно полезно, т.к. позволяет учитывать длительность матча. Независимость исходов отдельных фреймов является приближением к реальности, грубость которого оценивается полезностью итоговой модели.

Кстати, дополнительно поразмыслив над предложенным Вами способом, осознал, что я его недопонял. Почему-то думал, что агрегируются бинарные результаты фреймов и итоговая дельта есть <разность фреймов> * K * (1 — <вероятность победы в одном фрейме>) для победителя. При таком подходе дельта в матчах 4:0 и 18:14 действительно будет одинаковой. Если же явно суммировать дельты исходов отдельных фреймов, то результат может привести к загадочным последствиям. Например, сильный игрок выиграл матч у слабого, но отдал достаточно много фреймов. В результате рейтинг понижается, хотя цель (выиграть в матче) была достигнута.
Довод насчёт предположения о независимости, в принципе, похож на правду. Тем не менее, длительность матча при обычном и агрегирующем Эло в расчёт не берётся, что достаточно плохо.

Что Вы имеет в виду под «будет точнее»?
Спасибо за интерес к теме.
Рассматривал такой вариант. Мне в нём не понравилось две вещи:
  1. На данный момент у игроков не стоит задача выиграть матч с как можно большей разностью фреймов. Поэтому считать её показателем разности силы не очень корректно.
    В снукере цель «всего лишь» выиграть матч, т.е. выиграть определённое количество фреймов раньше соперника.

    Именно после формулирования этой фразы в голову пришла мысль считать вероятность конкретно такого исхода.
  2. Даже если у игроков будет стоять задача выигрывать с максимальной разностью фреймов, остаётся проблема с учётом различной длительности матчей. При таком последовательном вычислении суммарной дельты прирост у победителя в матче со счётом 18:17 будет меньше, чем при 4:0 (аж в четыре раза). Или, например, прирост будет одинаков в матчах 18:14 и 4:0. Такое свойство, как мне кажется, не справедливо отражает прирост «силы» игроков.
    Можно попробовать брать какую-то хитрую зависимость от разности фреймов и длительности матчей, но из-за предыдущего пункта я прекратил поиски в этом направлении.

Не совсем корректно написал. Имелось в виду "выиграет матч до одной победы… равна 0.404".

Меня больше удивили четвёртое место Селби и уверенное присутствие Джека Лисовски в топ-16.

1

Information

Rating
Does not participate
Registered
Activity