Загляните в эмоции вашего пространства Slack
Вы когда-нибудь задумывались, насколько увлекательным был доставленный вами контент? Было ясно или запутанно? Или если люди неправильно поняли ваше сообщение на той встрече в рамках всей компании?
Удаленная среда дает учителям и руководителям очень мало шансов получить обратную связь и оптимизировать свой контент для повышения производительности.
Поскольку значительная часть моей карьеры уже была проделана удаленно (на самом деле, еще до коронавируса!), Я обнаружил, что эти вопросы вызывают возбуждение и радость в моем мозгу, жаждущем творческих решений. У меня были данные, все, что мне нужно было сделать, это составить вопросы, на которые я хочу ответить, и сделать это.
Это также был последний e2e-проект моего Bootcamp, и у меня были профильные эксперты в качестве заинтересованных сторон (мой ведущий учитель и мой наставник по проекту), которые направили меня к созданию продукта, ориентированного на ценность.
Источник данных
Мой набор данных включал публичные обсуждения Slack с первого до последнего дня на Bootcamp Ironhack, предоставленном администратором Slack. Это можно было сделать и через Slack API, но это выходило за рамки моего проекта.
Чтобы сделать это сообщение кратким, обратите внимание, что я сосредоточился на выделении захватывающих частей кода, а не на выводах, которые он дал.
Если вы ищете:
- визуальные эффекты (выполненные в Tableau) посмотрите мою презентацию
- подробный код, просмотрите мое репозиторий GitHub здесь.
Очистка и борьба с данными
Чтобы представить проблему, с которой я столкнулся с количеством файлов JSON, вот просто папка общего канала, содержащая все разговоры с разбивкой по дням, например:
Итак, я начал с загрузки файлов JSON каждого канала в один фрейм данных.
# defining file path path_to_json = '../raw_data/general/' # get all json files from there json_pattern = os.path.join(path_to_json,'*.json') file_list = glob.glob(json_pattern) # an empty list to store the data frames dfs = [] for file in file_list: # read data frame from json file data = pd.read_json(file) # append the data frame to the list dfs.append(data) # concatenate all the data frames in the list channel_gen = pd.concat(dfs, ignore_index=True) # test channel_gen.tail(100)
Затем для удобства перешел к объединению отдельных фреймов данных каждого канала в один.
#frames = [channel_gen, channel_books, channel_dmemes, channel_dresource, channel_dbootcamp, channel_funcommittee, channel_dvizbeauties, channel_frustrations, channel_finalproject, channel_frustrations, channel_funcommittee, channel_katas, channel_labhelp, channel_music, channel_random, channel_vanilla] df = pd.concat([channel_gen, channel_books, channel_dmemes, channel_dresource, channel_dbootcamp, channel_funcommittee, channel_dvizbeauties, channel_frustrations, channel_finalproject, channel_frustrations, channel_funcommittee, channel_katas, channel_labhelp, channel_music, channel_random, channel_vanilla], ignore_index=True, join="outer")
К этому времени мой фрейм данных имеет 5263 строки и 13 столбцов с кучей не связанных с моим проектом данных. Уборка была тяжелой.
Столбцы, которые нужно очистить и обсудить:
- subtype: filter out it's values from df, remove the original column\
- ts: changing it to datetime, remove miliseconds, get days of the week, months of the year, type of the day, parts of the day\
- user_profile: extract real_name in new column, remove the original\
- attachments: extract title, text, link in new columns\
- files: extract url_private and who shared\
- attachments: extract title, text, link in new columns\
- reactions: extract user, count, name of the emoji\
Поскольку почти все данные были вложены в библиотеки JSON, большая часть времени моего проекта была потрачена на итерацию задач проектирования функций для создания переменных, которые я могу использовать для обучения моделей. В то же время извлечение данных - это то, что мне больше всего понравилось. Ниже вы можете увидеть несколько примеров функций, созданных для извлечения информации из фрейма данных.
Кто прислал больше всего ответов:
# user_profile column: extract real_name def getrealnamefromprofile(x): """this function is applied to column user_profile """ if x != x: return 'noname' else: return x['real_name'] df_clean['real_name'] = df_clean['user_profile'].apply(getrealnamefromprofile) df_clean
Какие смайлы использовались чаще всего в когорте:
# reactions column: extract frequency def getcountfromreactions(x): """this function is applied to column reactions """ if x != x: return 0 else: return x[0]['count'] df_clean['reactions_count'] = df_clean['reactions'].apply(getcountfromreactions) df_clean
Какими ссылками люди поделились на каналах:
# files column: extract link def geturlfromfile(x): """this function is applied to column files """ if x != x: return 'nofile' else: try: return x[0]['url_private'] except KeyError: return 'nolink_infiles' df_clean['link_of_file'] = df_clean['files'].apply(geturlfromfile) df_clean
Чтобы помочь мне найти источник общения, я создал еще одну функцию, чтобы отличать ведущего учителя и помощников учителя от учеников.
# create a new column with teaching and students def applyFunc(s): if s == 'siand the LT (she/her)': return 'teacher' if s == 'Florian Titze': return 'teacher' if s == 'Kosta': return 'teacher' else: return 'student' return '' df_clean['participant'] = df_clean['real_name'].apply(applyFunc) df_clean['participant'].value_counts()
Наконец, прежде чем приступить к работе с моделями, я с благодарностью посмотрел на очищенный фрейм данных:
Обработка естественного языка
Работая над этим разделом, я понял, что это то, на чем я хочу специализироваться. Текстовая аналитика звучит так круто, не так ли? Только представьте, сколько времени люди тратят на чтение громоздкого текста и попытки проанализировать его с помощью мозга, полного предубеждений, когда машина может сделать это за миллисекунды. Заставляет меня дрожать.
В мои задачи изначально входило извлечение текстовых функций (поскольку это самое ценное, что вы можете получить от письменного общения), и это то, над чем я работаю прямо сейчас, однако на это не было времени за те 5 дней, которые у меня были. и эта тема тоже выходила за рамки Bootcamp.
Вместо этого я сосредоточился на получении оценки настроения для каждого комментария и создании потрясающего мирового облака из наиболее часто используемых слов в качестве подарка моим коллегам. ❤️
Код
def clean_links(df): #replace URL of a text df_sent['text'] = df_sent['text'].str.replace('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', ' ') clean_links(df_sent) df_sent['text'] # load VADER sid = SentimentIntensityAnalyzer() # add VADER metrics to dataframe df_sent['scores'] = df_sent['text'].apply(lambda text: sid.polarity_scores(text)) df_sent['compound'] = df_sent['scores'].apply(lambda score_dict: score_dict['compound']) df_sent['comp_score'] = df_sent['compound'].apply(lambda c: 'pos' if c >=0 else 'neg') #test df_sent.head()
Это было легко. Теперь перейдем к сложной предварительной обработке, чтобы создать мировое облако без ссылок, цифр, знаков препинания и стоп-слов:
# set of stopwords to be removed from text stop = set(stopwords.words('english')) # update stopwords to have punctuation too stop.update(list(string.punctuation)) def clean_text(text_list): # Remove unwanted html characters re1 = re.compile(r' +') x1 = text_list.lower().replace('#39;', "'").replace('amp;', '&').replace('#146;', "'").replace( 'nbsp;', ' ').replace('#36;', '$').replace('\\n', "\n").replace('quot;', "'").replace( '<br />', "\n").replace('\\"', '"').replace('<unk>', 'u_n').replace(' @.@ ', '.').replace( ' @-@ ', '-').replace('\\', ' \\ ') text = re1.sub(' ', html.unescape(x1)) # remove non-ascii characters text = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('utf-8', 'ignore') # strip html soup = BeautifulSoup(text, 'html.parser') text = soup.get_text() # remove between square brackets text = re.sub('\[[^]]*\]', '', text) # remove URLs text = re.sub(r'http\S+', '', text) # remove twitter tags text = text.replace("@", "") # remove hashtags text = text.replace("#", "") # remove all non-alphabetic characters text = re.sub(r'[^a-zA-Z ]', '', text) # remove stopwords from text final_text = [] for word in text.split(): if word.strip().lower() not in stop: final_text.append(word.strip().lower()) text = " ".join(final_text) # lemmatize words lemmatizer = WordNetLemmatizer() text = " ".join([lemmatizer.lemmatize(word) for word in text.split()]) text = " ".join([lemmatizer.lemmatize(word, pos = 'v') for word in text.split()]) # replace all numbers with "num" text = re.sub("\d", "num", text) return text.lower() # apply cleaning function df_train['prep_text'] = df_train['text'].apply(clean_text) df_train['prep_text'].head(5) # apply wordcloud function make_wordcloud(df_train['prep_text'])
и результат: (та-даа)
Модели машинного обучения
Чтобы выделить здесь кое-что интересное, я взял модель классификации случайного леса, чтобы увидеть, какие функции вам понадобятся, чтобы получить ответ (и в данном случае получить помощь от когорты) с показателем точности 0,86:
# feature importance feat_importances = pd.Series(importances, index=X.columns) plt.figure(figsize=(10,10)) feat_importances.nlargest(15).plot(kind='barh', color='#FF9B48', width= 0.7) plt.xlabel('Level of importance', fontsize=16) plt.ylabel('Features', fontsize=16) plt.yticks([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14], ['length_of_text', 'neutral_tone', 'positive_tone', 'amount_of_reactions', 'negative_tone', 'may', 'morning','march', 'files_attached', 'teacher_posted', 'evening', 'early_morning', 'labhelp_channel', 'general_channel', 'got_reaction']) plt.title("Top 15 Important Features", fontsize=20) plt.show()
Похоже, у вас больше шансов получить ответ, если вы: напишете длинное сообщение нейтральным или позитивным тоном, получите много откликов на него, отправка его утром тоже поможет или если к нему прикреплен файл .
Заключение
В ходе этого проекта я узнал следующее:
- работа над тем, во что вы инвестируете, меняет правила игры
- наличие заинтересованных сторон на вашей стороне бесценно
- итерация - это ключ
- функции экономят ваше время в долгосрочной перспективе
Чтобы продолжить, я возьму этот набор данных и применю свои недавно полученные знания из Курса Udemy's NLP, чтобы извлечь кое-что интересное из комментариев.