Статьи

Сокращение 30 лет данных НБА с агрегацией MongoDB

Когда вы хотите запустить аналитику для больших и сложных наборов данных, вы можете инстинктивно обратиться к Hadoop. Однако, если ваши данные находятся в MongoDB, использование разъема Hadoop кажется чрезмерным, если ваши данные помещаются на вашем ноутбуке. К счастью, встроенная структура агрегации MongoDB  предлагает быстрое решение для запуска сложной аналитики прямо из вашего экземпляра MongoDB без необходимости какой-либо дополнительной настройки.

Как давний поклонник баскетбола, я часто мечтал о том, чтобы иметь возможность проводить сложный анализ статистики NBA. Итак, когда подошли дни хакатона MongoDB Driver Hackathon и ведущий Ruby-водитель Гэри Мураками предложил собрать интересный набор данных, мы сели и потратили полдня на сборку и работу скребка для basket-reference.com. Полученный набор данных содержит окончательный счет и баллы для каждой игры регулярного сезона НБА начиная с сезона 1985-1986 гг.

В документации инфраструктуры агрегации мы часто используем набор данных почтового индекса, чтобы проиллюстрировать использование структуры агрегации. Тем не менее, сжатые цифры о населении Соединенных Штатов не совсем поразили мое воображение, и есть определенные способы использования структуры агрегирования, которые набор данных почтовых индексов выделяет не так хорошо, как мог бы. Надеемся, что этот набор данных позволит вам по-новому взглянуть на структуру агрегации, пока вы немного покопаетесь в статистике NBA. Вы можете скачать набор данных здесь  и поместить его в свой экземпляр MongoDB, используя mongorestore .

Копаться в данных

Прежде всего, давайте посмотрим на структуру данных. С 1985 по 86 год было 31 686 игр регулярного сезона НБА. Каждый отдельный документ представляет игру. Вот метаданные высокого уровня для новичка сезона 1985-86 между Washington Bullets и Atlanta Hawks, представленного в RoboMongo, общем GUI MongoDB:

 

Документ содержит поддокумент с расширенными оценками, поле даты и информацию о командах, которые играли. Мы видим, что Пули выиграли 100-91 как дорожная команда. Данные о блоках боксов аналогично разбиваются на команды в массиве, в первую очередь на победу команды. Обратите внимание, что флаг победителя является членом объекта счета высшего уровня вместе с командой и игроками.

 

Количество очков для каждой команды дополнительно разбивается на статистику команды и статистику игрока. Вышеприведенные статистические данные команды показывают совокупную статистику для «Атланта Хокс», показывая, что они стреляли 41-92 с поля и зверские 9-18 с линии. Массив игроков показывает ту же статистику, но с разбивкой по отдельным игрокам. Например, ниже вы увидите, что звезда Хоукса Доминик Уилкинс набрала 32 очка на стрельбе 15-29 и записала 3 перехвата.

Запуск некоторых агрегаций

На высоком уровне структура агрегации MongoDB представлена ​​как функция оболочки, называемая агрегатом , которая принимает список этапов конвейера агрегации. Каждый этап конвейера работает с результатами предыдущего этапа, и каждый этап может фильтровать и преобразовывать отдельные документы.

Прежде чем приступить к серьезному анализу чисел, давайте начнем с простой проверки работоспособности и посчитаем, какие 5 команд получили больше всего побед в сезоне 1999-2000. Это может быть достигнуто с помощью 6-ступенчатого конвейера:

1) Используйте  этап $ match, чтобы ограничиться играми, которые проходили между 1 августа 1999 года и 1 августа 2000 года, двумя датами, которые достаточно далеки от любых игр NBA, чтобы безопасно связать сезон.

2) Используйте  этап $ unwind, чтобы сгенерировать один документ для каждой команды в игре.

3) Используйте $ match снова, чтобы ограничиться командами, которые выиграли.

4) Используйте этап $ group,  чтобы подсчитать, сколько раз данная команда появляется в выходных данных шага 3.

5) Используйте этап  сортировки $ для сортировки по количеству побед по убыванию.

6) Используйте стадию $ limit,  чтобы ограничиться 5-ю победившими командами.

Фактическая команда оболочки ниже. Эта команда выполняется в реальном времени на моем ноутбуке, даже без каких-либо индексов данных, потому что в коллекции всего 31 686 документов.

db.games.aggregate([
  {
    $match : {
      date : {
        $gt : ISODate("1999-08-01T00:00:00Z"),
        $lt : ISODate("2000-08-01T00:00:00Z")
      }
    }
  },
  {
    $unwind : '$teams'
  },
  {
    $match : {
      'teams.won' : 1
    }
  },
  {
    $group : {
      _id : '$teams.name',
      wins : { $sum : 1 }
    }
  },
  {
    $sort : { wins : -1 }
  },
  {
    $limit : 5
  }
]);

Мы можем расширить этот простой пример, чтобы ответить на вопрос о том, какая команда выиграла больше всего игр между сезоном 2000-2001 и сезоном 2009-2010, изменив шаг $ match, чтобы ограничиться играми, которые проходили между 1 августа 2000 года. и 1 августа 2010 года. Оказывается, Сан-Антонио Сперс выиграл 579 игр в тот период времени, чуть-чуть победив 568 Далласа Маверикс.

db.games.aggregate([
  {
    $match : {
      date : {
        $gt : ISODate("2000-08-01T00:00:00Z"),
        $lt : ISODate("2010-08-01T00:00:00Z")
      }
    }
  },
  {
    $unwind : '$teams'
  },
  {
    $match : {
      'teams.won' : 1
    }
  },
  {
    $group : {
      _id : '$teams.name',
      wins : { $sum : 1 }
    }
  },
  {
    $sort : { wins : -1 }
  },
  {
    $limit : 5
  }
]);

Сопоставление статистики с победами

Давайте делать что — то немного более интересным с помощью пары операторов агрегации , что вы не часто видим при анализе данных , почтовые индексы , набор: $ GTE оператор и Cond $ оператора в $ проектной стадии . Позволяет использовать их для вычисления того, как часто команда выигрывает, когда записывает больше защитных отскоков, чем их оппонент по всему набору данных.

Сложно получить представление о разнице между общей суммой восстановления защитной команды-победителя и общей суммой восстановления защитной команды-победителя. Структура агрегации делает вычисление разницы немного сложным, но используя $ cond , мы можем преобразовать документ так, чтобы общий защитный отскок был отрицательным, если команда проиграла. Затем мы можем использовать $ group для вычисления разницы в защитном отскоке для каждой игры. Давайте пройдемся по этому шаг за шагом:

1) Используйте $ unwind, чтобы получить документ, содержащий счет коробки для каждой команды в игре.

2) Используйте $ project с $ cond для трансформации каждого документа, чтобы защитный отскок команды был отрицательным, если команда проиграла, как определено флагом победителя.

3) Используйте $ group и $ sum, чтобы сложить итоговые суммы для каждой игры. Поскольку предыдущая стадия делала откат проигравшей команды в целом отрицательным, каждый документ теперь имеет разницу между защитными подборами победившей команды и защитными подборами проигравшей команды.

4) Используйте $ project и $ gte для создания документа, который имеет флаг WinningTeamHigher, который имеет значение true, если выигравшая команда имела больше защитных подборов, чем проигравшая команда.

5) Используйте $ group и $ sum, чтобы вычислить, сколько игр winTeamHigher было истинным.

db.games.aggregate([
  {
    $unwind : '$box'
  },
  {
    $project : {
      _id : '$_id',
      stat : {
        $cond : [
          { $gt : ['$box.won', 0] },
          '$box.team.drb',
          { $multiply : ['$box.team.drb', -1] }
        ]
      }
    }
  },
  {
    $group : {
      _id : '$_id',
      stat : { $sum : '$stat' }
    }
  },
  {
    $project : {
      _id : '$_id',
      winningTeamHigher : { $gte : ['$stat', 0] }
    }
  },
  {
    $group : {
      _id : '$winningTeamHigher',
      count : { $sum : 1 }
    }
  }
]);

Результат оказывается довольно интересным: команда, которая зафиксировала больше защитных подборов, выиграла примерно в 75% случаев. Чтобы представить это в перспективе, команда, которая записала больше голов, чем другая команда, выигрывает только 78,8% времени! Попробуйте переписать приведенную выше агрегацию для другой статистики, такой как цели, 3 указателя, обороты и т. Д. Вы найдете довольно интересные результаты. Наступательные подборы оказываются очень плохим предиктором того, какая команда выиграла, поскольку команда, которая зафиксировала больше наступательных подборов, выиграла только в 51% случаев. 3 указателя оказываются очень хорошим предсказателем того, какая команда выиграла: команда, которая записала больше 3 указателей, выиграла примерно в 64% случаев.

Защитный отскок и общий отскок от процента выигрыша

Давайте вычислим некоторые данные, связанные с этим, которые будут интересны для графика. Мы собираемся вычислить, какой процент времени команда выигрывает, как функцию количества защитных отскоков, которые они записали. Эта агрегация довольно проста, все, что нам нужно сделать, это $ развернуть счет коробки и использовать $ group, чтобы вычислить среднее значение выигранного флага по каждому разному защитному отскочившему итогу.

db.games.aggregate([
  {
    $unwind : '$box'
  },
  {
    $group : {
      _id : '$box.team.drb',
      winPercentage : { $avg : '$box.won' }
    }
  },
  {
    $sort : { _id : 1 }
  }
]);

И когда мы планируем результаты этой агрегации, мы можем создать хороший график, который аккуратно показывает довольно твердую корреляцию между защитными отскоками и процентом выигрыша. Интересный фактоид: команда, которая зафиксировала наименьшее количество защитных подборов в победе, была Торонто Рапторс 1995-96 гг., Победившая Милуоки Бакс 93-87 26.12.1995, несмотря на то, что записала только 14 защитных подборов.

Мы можем довольно легко изменить приведенную выше агрегацию, чтобы вычислить одно и то же распределение для общего отскока (TRB) по сравнению с защитным отскоком и посмотреть, получим ли мы другой результат.

db.games.aggregate([
  {
    $unwind : '$box'
  },
  {
    $group : {
      _id : '$box.team.trb',
      winPercentage : { $avg : '$box.won' }
    }
  },
  {
    $sort : { _id : 1 }
  }
]);

И на самом деле мы делаем! Приблизительно после 53 отскоков положительная корреляция между отскоком и процентом выигрыша полностью исчезает! Корреляция здесь определенно не такая сильная, как для защитных подборов. Кроме того, «Кливленд Кавальерс» победили «Нью-Йорк Никс 101-197» 11 апреля 1996 года, несмотря на то, что зафиксировали всего 21 отскок. И наоборот, «Сан-Антонио Сперс» уступили «Хьюстон Рокетс» 112–110 4 января 1992 года, несмотря на рекордные 75 отскоков.

Вывод

Я надеюсь, что этот пост в блоге так же вдохновил вас, как и я. Еще раз, вы можете скачать набор данных здесь , и вам очень рекомендуется играть с ним самостоятельно. Я с нетерпением жду возможности увидеть, какие уникальные анализы НБА вам придутся.

Юридическое примечание: прилагаемый набор данных является собственностью Sports Reference, LLC и может быть использован только для обучения и оценки в соответствии с пунктом # 1 их условий использования . Если вы не прочитали или не согласны с условиями использования Sports Reference, LLC , не загружайте набор данных.