При поиске по ключевой фразе «машинное обучение» (речь идет об англоязычном ключе «machine learning» — прим. перев.) я нашел 246632 репозиториев по машинному обучению. Поскольку все они имеют отношение к этой отрасли, я ожидал, что их владельцы являются экспертами или хотя бы достаточно компетентны в машинном обучении. Поэтому я решил проанализировать профили этих пользователей и показать результаты анализа.
Как я работал
Инструменты
Я использовал три инструмента для скрейпинга:
- Beautiful Soup для извлечения URL всех репозиториев с тегом «машинное обучение». Это Python библиотека, которая позволяет значительно упростить скрейпинг.
- PyGithub позволяет извлекать информацию о пользователях. Это еще одна Python-библиотека для использования с Github API v3. Она дает возможность управлять различными Github-ресурсами (репозитории, профили пользователей, организации и т.п.) при помощи Python-скриптов.
- Requests для извлечения информации о репозиториях и линков на профили контрибьюторов.
Методы
Я спарсил далеко не все, а лишь владельцев и 30 самых активных контрибьюторов 90 топовых репозиториев, которые оказались в результатах поиска.
После удаления дублей и профилей организаций вроде udacity, я получил список из 1208 пользователей. Для каждого из них я спарсил информацию по 20 ключевым параметрам.
new_profile.info()
Первые 13 параметров были получены отсюда.
Оставшиеся я взял из репозиториев пользователя:
- total_stars общее количество звезд всех репозиториев
- max_star максимальное количество звезд всех репозиториев
- forks общее количество форков всех репозиториев
- descriptions описания из всех репозиториев пользователя всех репо
- contribution количество контрибуций за последний год
Визуализируем данные
Гистограммы
После очистки данных наступил черед самого интересного этапа: визуализации данных. Для этого я использовал Plotly.
import matplotlib.pyplot as plt
import numpy as np
import plotly.express as px # for plotting
import altair as alt # for plotting
import datapane as dp # for creating a report for your findings
top_followers = new_profile.sort_values(by='followers', axis=0, ascending=False)
fig = px.bar(top_followers,
x='user_name',
y='followers',
hover_data=['followers'],
)
fig.show()
Вот что получилось.
Гистограмма немного неудобна, поскольку в ней очень длинный «хвост» пользователей с количеством фолловеров ниже 100. Так что лучше увеличить все это.
Как видим, у llSourcell (Siraj Raval) больше всего фолловеров (36261). У второго по популярности количество фолловеров меньше в три раза (12682).
Мы можем пойти дальше и выяснить, что 1% профилей получил 41% всех фолловеров!
>>> top_n = int(len(top_followers) * 0.01)12>>> sum(top_followers.iloc[0: top_n,:].loc[:, 'followers'])/sum(top_followers.followers)0.41293075864408607
Далее визуализируем информацию по total_stars, max_star, forks при помощи логарифмической шкалы.
figs = [] # list to save all the plots and table
features = ['followers',
'following',
'total_stars',
'max_star',
'forks',
'contribution']
for col in features:
top_col = new_profile.sort_values(by=col, axis=0, ascending=False)
log_y = False
#change scale of y-axis of every feature to log except contribution
if col != 'contribution':
log_y = True
fig = px.bar(top_col,
x='user_name',
y=col,
hover_data=[col],
log_y = log_y
)
fig.update_layout({'plot_bgcolor': 'rgba(36, 83, 97, 0.06)'}) #change background coor
fig.show()
figs.append(dp.Plot(fig))
Получается вот так.
Полученная картина очень близка к распределению по закону Ципфа. Речь идет об эмпирической закономерности распределения частоты слов естественного языка: если все слова языка упорядочить по убыванию частоты их использования. Похожая зависимость есть у нас и здесь.
Корреляция
Но что насчет зависимостей между ключевыми точками данных? И насколько эти зависимости сильны? Я использовал scatter_matrix для того, чтобы это выяснить.
correlation = px.scatter_matrix(new_profile, dimensions=['forks', 'total_stars', 'followers',
'following', 'max_star','contribution'],
title='Correlation between datapoints',
width=800, height=800)
correlation.show()
corr = new_profile.corr()
figs.append(dp.Plot(correlation))
figs.append(dp.Table(corr))
corr
Получается вот так и еще так.
Наиболее сильные положительные зависимости образуются между:
- Максимальным количеством звезд и общим их количеством (0.939)
- Количество форков и общим количеством звезд (0,929)
- Количеством форков и количеством фолловеров (0,774)
- Количеством фолловеров и общим количеством звезд (0,632)
Языки программирования
Для того, чтобы выяснить наиболее распространенные среди владельцев профилей GitHub языки программирования, я провел дополнительный анализ.
# Collect languages from all repos of al users
languages = []
for language in list(new_profile['languages']):
try:
languages += language
except:
languages += ['None']
# Count the frequency of each language
from collections import Counter
occ = dict(Counter(languages))
# Remove languages below count of 10
top_languages = [(language, frequency) for language, frequency in occ.items() if frequency > 10]
top_languages = list(zip(*top_languages))
language_df = pd.DataFrame(data = {'languages': top_languages[0],
'frequency': top_languages[1]})
language_df.sort_values(by='frequency', axis=0, inplace=True, ascending=False)
language = px.bar(language_df, y='frequency', x='languages',
title='Frequency of languages')
figs.append(dp.Plot(language))
language.show()
Соответственно, в топ-10 языков входят:
- Python
- JavaScript
- HTML
- Jupyter Notebook
- Shell и т.п.
Местонахождение
Для того, чтобы понять, в каких частях света находятся владельцы профилей, нужно выполнить следующую задачу — визуализировать местонахождение пользователей. Среди проанализированных профилей география указана для 31%. Для визуализации используем geopy.geocoders.Nominatim
from geopy.geocoders import Nominatim
import folium
geolocator = Nominatim(user_agent='my_app')
locations = list(new_profile['location'])
# Extract lats and lons
lats = []
lons = []
exceptions = []
for loc in locations:
try:
location = geolocator.geocode(loc)
lats.append(location.latitude)
lons.append(location.longitude)
print(location.address)
except:
print('exception', loc)
exceptions.append(loc)
print(len(exceptions)) # output: 17
# Remove the locations not found in map
location_df = new_profile[~new_profile.location.isin(exceptions)]
location_df['latitude'] = lats
location_df['longitude'] = lons
Ну а затем, для построения карты используем Plotly’s scatter_geo
# Visualize with Plotly's scatter_geo
m = px.scatter_geo(location_df, lat='latitude', lon='longitude',
color='total_stars', size='forks',
hover_data=['user_name','followers'],
title='Locations of Top Users')
m.show()
figs.append(dp.Plot(m))
По этой ссылке доступна оригинальная карта с зумом.
Описание репо и био пользователей
Многие пользователи оставляют описание для своих репозиториев, а также предоставляют собственные био. Для того, чтобы все это визуализировать, используем WordCloud! для Python.
import string
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenize
from wordcloud import WordCloud, STOPWORDS
import matplotlib.pyplot as plt
nltk.download('stopwords')
nltk.download('punkt')
nltk.download('wordnet')
def process_text(features):
'''Function to process texts'''
features = [row for row in features if row != None]
text = ' '.join(features)
# lowercase
text = text.lower()
#remove punctuation
text = text.translate(str.maketrans('', '', string.punctuation))
#remove stopwords
stop_words = set(stopwords.words('english'))
#tokenize
tokens = word_tokenize(text)
new_text = [i for i in tokens if not i in stop_words]
new_text = ' '.join(new_text)
return new_text
def make_wordcloud(new_text):
'''Function to make wordcloud'''
wordcloud = WordCloud(width = 800, height = 800,
background_color ='white',
min_font_size = 10).generate(new_text)
fig = plt.figure(figsize = (8, 8), facecolor = None)
plt.imshow(wordcloud)
plt.axis("off")
plt.tight_layout(pad = 0)
plt.show()
return fig
descriptions = []
for desc in new_profile['descriptions']:
try:
descriptions += desc
except:
pass
descriptions = process_text(descriptions)
cloud = make_wordcloud(descriptions)
figs.append(dp.Plot(cloud))
И то же самое для био
bios = []
for bio in new_profile['bio']:
try:
bios.append(bio)
except:
pass
text = process_text(bios)
cloud = make_wordcloud(text)
figs.append(dp.Plot(cloud))
Как видим, ключевые слова вполне соответствуют тому, чего можно ожидать от специалистов по машинному обучению.
Выводы
Данные получены от пользователей и авторов 90 репозиториев с оптимальным соответствием ключу «machine learning». Но гарантии того, что в список попали все ведущие владельцы профилей, эксперты по машинному обучению нет.
Тем не менее статья — хороший пример того, как можно очистить собранные данные и визуализировать их. Скорее всего, результат вас удивит. И в этом нет ничего странного, поскольку data science помогает применить свои знания для анализа того, что вас окружает.
Ну и если нужно — можете форкать код этой статьи и делать с ним что угодно, вот репо</a.
Комментарий к статье Вячеслава Архипова, специалиста в области Data Science AR-стартапа Banuba и консультанта по учебной программе онлайн-университета Skillbox.
Интересное исследование, показывающее все шаги статистического анализа «рынка» github-профилей по машинному обучению. Аналогичным образом можно было бы проанализировать соцсети, спрос на товары, предпочтения кинозрителей и т.д.
Хотелось бы обратить внимание на два факта:
1) несмотря на утверждение автора, что для визуализации показателей профиля используется логарифмическая шкала, на самом деле используются линейные шкалы. Это видно из приведенного листинга (из него следует, что логарифмическая шкала использовалась только для 'contribution'). Все графики выглядят, как построенные в линейной шкале.
А вот само распределение показателя 'contribution' действительно похоже на распределение Парето, известное в применении к частотам встречаемости слов как распределение Ципфа. Графики других показателей мне не очень напоминают распределение Парето.
Тут хотелось бы увидеть использование критерия Пирсона, для оценки правдоподобия того, что исследуемые данные имеют определенное распределение. Тем более, что есть даже готовая гипотеза о том, что это распределение Парето (Ципфа).
2) Мне кажется, было бы интересно поглубже раскрыть тему корреляции показателей. Автор отметил показатели, которые имеют очевидно высокую корреляцию. Но, коэффициент корреляции после небольшого преобразования можно превратить в величину, имеющую распределение Стьюдента. А значит, можно провести исследование на значимость коэффициента корреляции, приняв нуль-гипотезу о линейной независимости параметров. Учитывая большое количество исследованных данных (степеней свободы), порог для отвержения нуль-гипотезы будет довольно низким и станет очевидно, что еще многие параметры имеют хоть и небольшую по значению, но статистически значимую корреляцию.
А следующим шагом можно попытаться оценить размерность: понять, сколькими параметрами можно описать одну точку данных. А также найти эти скрытые параметры методом главных компонент.