Загляните в эмоции вашего пространства 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, чтобы извлечь кое-что интересное из комментариев.