Dans cet article, je vais vous montrer comment utiliser Wordcloud pour visualiser les sujets qui ont été soulevés dans la communauté Slack sur une période donnée (1 semaine ici).
Le code source peut être trouvé ici: octocat:
Je veux aussi lire: [Traitement du langage naturel] J'ai essayé de visualiser les remarques de chaque membre de la communauté Slack
* Je voudrais résumer le prétraitement dans un autre article à l'avenir </ font>
Pour plus d'informations, consultez Mise en route dans README. Le flux est comme ça.
docker-compose up -d
docker exec -it ds-py3 bash
run_wordcloud_by_term.sh
Ceci est un exemple de sortie réelle. Wordcloud est la remarque de chaque période différente.
Obtenez des jetons d'API Slack auprès de Slack API Official
Comment démarrer avec l'API Slack n'est pas montré ici.
Pour effectuer le traitement ultérieur, veuillez obtenir les jetons suivants
Ici, nous allons créer une classe ** SlackApp ** qui obtient des informations Slack via l'API. Les informations acquises sont enregistrées au format JSON sans traitement.
slack_app.py
#Une classe qui utilise slackapi pour obtenir les informations souhaitées(Ne pas traiter)
import requests
import json
from tqdm import tqdm
import pandas as pd
class SlackApp:
ch_list_url = 'https://slack.com/api/channels.list'
ch_history_url = 'https://slack.com/api/channels.history'
usr_list_url = 'https://slack.com/api/users.list'
def __init__(self, ch_api_key, usr_api_key):
# NEW members
self.channels_info = []
self.users_info = []
self.messages_info = []
# OLD members
self.channelInfo = {} # k: ch_name, v: ch_id
self.messages_in_chs = {}
self.userInfo = {}
self.ch_api_token = str(ch_api_key)
self.usr_api_token = str(usr_api_key)
def load_save_channel_info(self, outdir: str):
#Obtenez des informations sur la chaîne via l'API Slack et enregistrez-les dans un fichier
payload = {'token': self.ch_api_token}
response = requests.get(SlackApp.ch_list_url, params=payload)
if response.status_code == 200:
json_data = response.json()
if 'channels' in json_data.keys():
self.channels_info = json_data['channels']
with open(outdir + '/' + 'channel_info.json', 'w', encoding='utf-8') as f:
json.dump(self.channels_info, f, indent=4, ensure_ascii=False)
def load_save_user_info(self, outdir: str):
#Obtenez des informations sur l'utilisateur via l'API Slack et enregistrez-les dans un fichier
payload = {'token': self.usr_api_token}
response = requests.get(SlackApp.usr_list_url, params=payload)
if response.status_code == 200:
json_data = response.json()
if 'members' in json_data.keys():
self.users_info = json_data['members']
with open(outdir + '/' + 'user_info.json', 'w', encoding='utf-8') as f:
json.dump(self.users_info, f, indent=4, ensure_ascii=False)
def load_save_messages_info(self, outdir: str):
#Créer une liste d'identifiants de chaînes
channel_id_list = []
for ch in self.channels_info:
channel_id_list.append(ch['id'])
#Obtenez des informations sur l'utilisateur via l'API Slack et enregistrez-les dans un fichier
for ch_id in tqdm(channel_id_list, desc='[loading...]'):
payload = {'token': self.ch_api_token, 'channel': ch_id}
response = requests.get(SlackApp.ch_history_url, params=payload)
if response.status_code == 200:
json_data = response.json()
msg_in_ch = {}
msg_in_ch['channel_id'] = ch_id
if 'messages' in json_data.keys():
msg_in_ch['messages'] = json_data['messages']
else:
msg_in_ch['messages'] = ''
self.messages_info.append(msg_in_ch)
with open(outdir + '/' + 'messages_info.json', 'w', encoding='utf-8') as f:
json.dump(self.messages_info, f, indent=4, ensure_ascii=False)
Utilisez la classe ** SlackApp ** plus tôt pour obtenir les informations.
Les informations à acquérir sont les trois suivantes
slack_msg_extraction.py
#Une classe qui utilise slackapi pour obtenir les informations souhaitées
import sys
import json
sys.path.append('../../src/d000_utils') #Ajout du chemin de stockage pour les scripts SlackApp
import slack_app as sa
def main():
# -------------------------------------
# load api token
# -------------------------------------
credentials_root = '../../conf/local/'
credential_fpath = credentials_root + 'credentials.json'
print('load credential.json ...')
with open(credential_fpath, 'r') as f:
credentials = json.load(f)
# -------------------------------------
# start slack app
# -------------------------------------
print('start slack app ...')
app = sa.SlackApp(
credentials['channel_api_key'],
credentials['user_api_key']
)
outdir = '../../data/010_raw'
# -------------------------------------
# get channels info
# -------------------------------------
app.load_save_channel_info(outdir)
# -------------------------------------
# get user info
# -------------------------------------
app.load_save_user_info(outdir)
# -------------------------------------
# get msg info
# -------------------------------------
app.load_save_messages_info(outdir)
if __name__ == "__main__":
main()
Les informations acquises par SlackAPI ont été enregistrées au format JSON selon les spécifications de SlackAPI. Mettez en forme les données du tableau afin qu'elles puissent être facilement analysées. Lors de la conception d'une table, tenez compte de Tidy Data.
Cette fois, je l'ai conçu comme suit. J'ai fait le tableau suivant avec l'image des informations minimales requises + α.
message mart table
No | Column Name | Type | Content |
---|---|---|---|
0 | index | int | AUTO INCREMENT |
1 | ch_id | str | Identifiant de la chaine |
2 | msg | str | Chaîne de message |
3 | uid | str | ID utilisateur de l'orateur |
4 | timestamp | datetime | Il est temps de parler |
No | Column Name | Type | Content |
---|---|---|---|
0 | index | int | AUTO INCREMENT |
1 | ch_id | str | Identifiant de la chaine |
2 | ch_name | str | Nom du canal (nom affiché sur l'interface utilisateur de Slack) |
3 | ch_namenorm | str | Nom de canal normalisé |
4 | ch_membernum | int | Nombre de participants à la chaîne |
No | Column Name | Type | Content |
---|---|---|---|
0 | index | int | AUTO INCREMENT |
1 | uid | str | Identifiant d'utilisateur |
2 | uname | str | Nom d'utilisateur |
Le code réel est ci-dessous.
../../ data / 010_raw
: informations obtenues à partir de l'emplacement de stockage au format Slack JSON
--ʻUser_info.json: nom du fichier d'informations utilisateur (JSON) --
messages_info.json: nom du fichier d'informations de message (JSON) pour tous les canaux --
channel_info.json`: nom du fichier d'informations de canal (JSON)make_msg_mart_table.py
import json
import pandas as pd
def make_user_table(usr_dict: dict) -> pd.DataFrame:
uid_list = []
uname_list = []
for usr_ditem in usr_dict:
if usr_ditem['deleted'] == True:
continue
uid_list.append(usr_ditem['id'])
uname_list.append(usr_ditem['profile']['real_name_normalized'])
user_table = pd.DataFrame({'uid': uid_list, 'uname': uname_list})
return user_table
def make_msg_table(msg_dict: dict) -> pd.DataFrame:
ch_id_list = []
msg_list = []
uid_list = []
ts_list = []
for msg_ditem in msg_dict:
if 'channel_id' in msg_ditem.keys():
ch_id = msg_ditem['channel_id']
else:
continue
if 'messages' in msg_ditem.keys():
msgs_in_ch = msg_ditem['messages']
else:
continue
# get message in channel
for i, msg in enumerate(msgs_in_ch):
# if msg by bot, continue
if 'user' not in msg:
continue
ch_id_list.append(ch_id)
msg_list.append(msg['text'])
uid_list.append(msg['user']) #Il n'y a pas cette clé pour le bot
ts_list.append(msg['ts'])
df_msgs = pd.DataFrame({
'ch_id': ch_id_list,
'msg': msg_list,
'uid': uid_list,
'timestamp': ts_list
})
return df_msgs
def make_ch_table(ch_dict: dict) -> pd.DataFrame:
chid_list = []
chname_list = []
chnormname_list = []
chmembernum_list = []
for ch_ditem in ch_dict:
chid_list.append(ch_ditem['id'])
chname_list.append(ch_ditem['name'])
chnormname_list.append(ch_ditem['name_normalized'])
chmembernum_list.append(ch_ditem['num_members'])
ch_table = pd.DataFrame({
'ch_id': chid_list,
'ch_name': chname_list,
'ch_namenorm': chnormname_list,
'ch_membernum': chmembernum_list
})
return ch_table
def main():
# 1. load user/message/channels
input_root = '../../data/010_raw'
user_info_fpath = input_root + '/' + 'user_info.json'
with open(user_info_fpath, 'r', encoding='utf-8') as f:
user_info_rawdict = json.load(f)
print('load ... ', user_info_fpath)
msg_info_fpath = input_root + '/' + 'messages_info.json'
with open(msg_info_fpath, 'r', encoding='utf-8') as f:
msgs_info_rawdict = json.load(f)
print('load ... ', msg_info_fpath)
ch_info_fpath = input_root + '/' + 'channel_info.json'
with open(ch_info_fpath, 'r', encoding='utf-8') as f:
ch_info_rawdict = json.load(f)
print('load ... ', ch_info_fpath)
# 2. make and save tables
# user
output_root = '../../data/020_intermediate'
df_user_info = make_user_table(user_info_rawdict)
user_tbl_fpath = output_root + '/' + 'users.csv'
df_user_info.to_csv(user_tbl_fpath, index=False)
print('save ... ', user_tbl_fpath)
# msg
df_msg_info = make_msg_table(msgs_info_rawdict)
msg_tbl_fpath = output_root + '/' + 'messages.csv'
df_msg_info.to_csv(msg_tbl_fpath, index=False)
print('save ... ', msg_tbl_fpath)
# channel
df_ch_info = make_ch_table(ch_info_rawdict)
ch_tbl_fpath = output_root + '/' + 'channels.csv'
df_ch_info.to_csv(ch_tbl_fpath, index=False)
print('save ... ', ch_tbl_fpath)
if __name__ == "__main__":
main()
Fait généralement référence à l'action de supprimer le bruit Il est nécessaire d'effectuer divers traitements en fonction des données cibles et du but. Ici, le traitement suivant a été exécuté.
cleaning.py
import re
import pandas as pd
import argparse
from pathlib import Path
def clean_msg(msg: str) -> str:
# sub 'Return and Space'
result = re.sub(r'\s', '', msg)
# sub 'url link'
result = re.sub(r'(<)http.+(>)', '', result)
# sub 'mention'
result = re.sub(r'(<)@.+\w(>)', '', result)
# sub 'reaction'
result = re.sub(r'(:).+\w(:)', '', result)
# sub 'html key words'
result = re.sub(r'(&).+?\w(;)', '', result)
# sub 'multi lines code block'
result = re.sub(r'(```).+(```)', '', result)
# sub 'inline code block'
result = re.sub(r'(`).+(`)', '', result)
return result
def clean_msg_ser(msg_ser: pd.Series) -> pd.Series:
cleaned_msg_list = []
for i, msg in enumerate(msg_ser):
cleaned_msg = clean_msg(str(msg))
if 'Rejoint la chaîne' in cleaned_msg:
continue
cleaned_msg_list.append(cleaned_msg)
cleaned_msg_ser = pd.Series(cleaned_msg_list)
return cleaned_msg_ser
def get_ch_id_from_table(ch_name_parts: list, input_fpath: str) -> list:
df_ch = pd.read_csv(input_fpath)
ch_id = []
for ch_name_part in ch_name_parts:
for i, row in df_ch.iterrows():
if ch_name_part in row.ch_name:
ch_id.append(row.ch_id)
break
return ch_id
def main(input_fname: str):
input_root = '../../data/020_intermediate'
output_root = input_root
# 1. load messages.csv (including noise)
msgs_fpath = input_root + '/' + input_fname
df_msgs = pd.read_csv(msgs_fpath)
print('load :{0}'.format(msgs_fpath))
# 2. Drop Not Target Records
print('drop records (drop non-target channel\'s messages)')
non_target_ch_name = ['general', 'Annonce de la direction']
non_target_ch_ids = get_ch_id_from_table(non_target_ch_name, input_root + '/' + 'channels.csv')
print('=== non target channels bellow ====')
print(non_target_ch_ids)
for non_target_ch_id in non_target_ch_ids:
df_msgs = df_msgs.query('ch_id != @non_target_ch_id')
# 3. clean message string list
ser_msg = df_msgs.msg
df_msgs.msg = clean_msg_ser(ser_msg)
# 4. save it
pin = Path(msgs_fpath)
msgs_cleaned_fpath = output_root + '/' + pin.stem + '_cleaned.csv'
df_msgs.to_csv(msgs_cleaned_fpath, index=False)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("input_fname", help="set input file name", type=str)
args = parser.parse_args()
input_fname = args.input_fname
main(input_fname)
En fait, je voulais "supprimer uniquement lorsque le bloc de code contient du code source", mais je ne pouvais pas. : transpiration:
Généralement, c'est le processus de recherche de «morphologie» dans une phrase. Les détails de l'analyse morphologique seront laissés à d'autres articles.
Le véritable objectif ici est de réaliser un «partage».
En gros, c'est le processus de conversion d'une phrase en information appelée "mot mot mot". Par exemple
** Exemple de phrase: J'aime le baseball. ** ** ** Exemple de segmentation de phrase: "J'aime le baseball" **
Ce sera sous la forme de.
En pratique, si vous souhaitez gérer des mots qui représentent le contexte d'une phrase comme celle-ci, extract "nouns" </ font> est préférable. Donc,
** Séparation d'exemples de phrases (nom uniquement): "Private baseball" **
Ce serait encore mieux.
Si vous souhaitez mettre en œuvre une analyse morphologique
--Que utiliser pour la bibliothèque d'analyse morphologique --Que utiliser comme données de dictionnaire
Nous devons en décider.
Cette fois, j'ai fait ce qui suit.
--Bibliothèque d'analyse morphologique: Janome --Dictionary data: Janome default (NEologd pour les nouveaux mots est encore mieux)
De plus, les mots de partie à extraire sont ceux nécessaires pour atteindre l'objectif en jetant un coup d'œil à "Système de partie de partie de l'outil d'analyse morphologique". quelque chose? J'ai pensé du point de vue.
Janome, la mascotte officielle, est mignonne. (Je ne sais pas si Janome est le nom)
morphological_analysis.py
from janome.tokenizer import Tokenizer
from tqdm import tqdm
import pandas as pd
import argparse
from pathlib import Path
import sys
exc_part_of_speech = {
"nom": ["Non indépendant", "代nom", "nombre"]
}
inc_part_of_speech = {
"nom": ["Changer de connexion", "Général", "固有nom"],
}
class MorphologicalAnalysis:
def __init__(self):
self.janome_tokenizer = Tokenizer()
def tokenize_janome(self, line: str) -> list:
# list of janome.tokenizer.Token
tokens = self.janome_tokenizer.tokenize(line)
return tokens
def exists_pos_in_dict(self, pos0: str, pos1: str, pos_dict: dict) -> bool:
# Retrurn where pos0, pos1 are in pos_dict or not.
# ** pos = part of speech
for type0 in pos_dict.keys():
if pos0 == type0:
for type1 in pos_dict[type0]:
if pos1 == type1:
return True
return False
def get_wakati_str(self, line: str, exclude_pos: dict,
include_pos: dict) -> str:
'''
exclude/include_pos is like this
{"nom": ["Non indépendant", "代nom", "nombre"], "adjectif": ["xxx", "yyy"]}
'''
tokens = self.janome_tokenizer.tokenize(line, stream=True) #Générateur pour économiser de la mémoire
extracted_words = []
for token in tokens:
part_of_speech0 = token.part_of_speech.split(',')[0]
part_of_speech1 = token.part_of_speech.split(',')[1]
# check for excluding words
exists = self.exists_pos_in_dict(part_of_speech0, part_of_speech1,
exclude_pos)
if exists:
continue
# check for including words
exists = self.exists_pos_in_dict(part_of_speech0, part_of_speech1,
include_pos)
if not exists:
continue
# append(Forme de couche de surface acquise pour absorber les fluctuations de notation)
extracted_words.append(token.surface)
# wakati string with extracted words
wakati_str = ' '.join(extracted_words)
return wakati_str
def make_wakati_for_lines(msg_ser: pd.Series) -> pd.Series:
manalyzer = MorphologicalAnalysis()
wakati_msg_list = []
for msg in tqdm(msg_ser, desc='[mk wakati msgs]'):
wakati_msg = manalyzer.get_wakati_str(str(msg), exc_part_of_speech,
inc_part_of_speech)
wakati_msg_list.append(wakati_msg)
wakati_msg_ser = pd.Series(wakati_msg_list)
return wakati_msg_ser
def main(input_fname: str):
input_root = '../../data/020_intermediate'
output_root = '../../data/030_processed'
# 1. load messages_cleaned.csv
msgs_cleaned_fpath = input_root + '/' + input_fname
df_msgs = pd.read_csv(msgs_cleaned_fpath)
# 2. make wakati string by record
ser_msg = df_msgs.msg
df_msgs['wakati_msg'] = make_wakati_for_lines(ser_msg)
# 3. save it
pin = Path(msgs_cleaned_fpath)
msgs_wakati_fpath = output_root + '/' + pin.stem + '_wakati.csv'
df_msgs.to_csv(msgs_wakati_fpath, index=False)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("input_fname", help="set input file name", type=str)
args = parser.parse_args()
input_fname = args.input_fname
# input file must been cleaned
if 'cleaned' not in input_fname:
print('input file name is invalid.: {0}'.format(input_fname))
print('input file name must include \'cleaned\'')
sys.exit(1)
main(input_fname)
La normalisation dans le prétraitement du traitement du langage naturel fait référence au traitement suivant. Il est également appelé «identification du nom».
Le monde de la normalisation est profond, je vais donc le laisser ici.
Cette fois, par souci de simplicité, seul le traitement suivant a été mis en œuvre. C'est incroyablement facile.
normalization.py
import re
import pandas as pd
from tqdm import tqdm
import argparse
from pathlib import Path
import sys
def normarize_text(text: str):
normalized_text = normalize_number(text)
normalized_text = lower_text(normalized_text)
return normalized_text
def normalize_number(text: str) -> str:
"""
pattern = r'\d+'
replacer = re.compile(pattern)
result = replacer.sub('0', text)
"""
#Remplacer les nombres consécutifs par 0
replaced_text = re.sub(r'\d+', '0', text)
return replaced_text
def lower_text(text: str) -> str:
return text.lower()
def normalize_msgs(wktmsg_ser: pd.Series) -> pd.Series:
normalized_msg_list = []
for wktstr in tqdm(wktmsg_ser, desc='normalize words...'):
normalized = normarize_text(str(wktstr))
normalized_msg_list.append(normalized)
normalized_msg_ser = pd.Series(normalized_msg_list)
return normalized_msg_ser
def main(input_fname: str):
input_root = '../../data/030_processed'
output_root = input_root
# 1. load wakati messages
msgs_fpath = input_root + '/' + input_fname
df_msgs = pd.read_csv(msgs_fpath)
# 2. normalize wakati_msg (update)
ser_msg = df_msgs.wakati_msg
df_msgs.wakati_msg = normalize_msgs(ser_msg)
# 3. save it
pin = Path(msgs_fpath)
msgs_ofpath = output_root + '/' + pin.stem + '_norm.csv'
df_msgs.to_csv(msgs_ofpath, index=False)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("input_fname", help="set input file name", type=str)
args = parser.parse_args()
input_fname = args.input_fname
# input file must been cleaned
if 'wakati' not in input_fname:
print('input file name is invalid.: {0}'.format(input_fname))
print('input file name must include \'wakati\'')
sys.exit(1)
main(input_fname)
"Suppression des mots vides" est le processus de suppression des mots vides. (Trop comme ça ...)
Alors qu'est-ce qu'un "** mot d'arrêt **"?
Selon le dictionnaire de langue nationale goo
Mots qui sont si courants dans les recherches en texte intégral qu'ils sont exclus de la recherche par eux-mêmes. Fait référence à "ha", "no", "desu" en japonais et a, the, of en anglais. Mots vides.
Les tâches de traitement du langage naturel ont souvent pour but de «comprendre le contexte d'une phrase». Les mots vides ne sont pas nécessaires à cette fin et doivent être supprimés.
Il y en a deux principaux. Cette fois, nous utiliserons 1.
Pour les données de dictionnaire, utilisez here.
Il y a plusieurs raisons de choisir la méthode 1.
--Lors de l'utilisation d'un dictionnaire Dictionnaire existant facilite l'installation.
Supprime les mots enregistrés dans les données de dictionnaire introduites dans la section précédente. De plus, les caractères suivants ont également été supprimés. C'est un mot que j'ai trouvé ennuyeux en réglant diverses choses.
stopword_removal.py
import pandas as pd
import urllib.request
from pathlib import Path
from tqdm import tqdm
import argparse
from pathlib import Path
import sys
def maybe_download(path: str):
stopword_def_page_url = 'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt'
p = Path(path)
if p.exists():
print('File already exists.')
else:
print('downloading stop words definition ...')
# Download the file from `url` and save it locally under `file_name`:
urllib.request.urlretrieve(stopword_def_page_url, path)
#mot d'arrêt ajouté
sw_added_list = [
'-',
'-',
'w',
'W',
'm',
'Lol'
]
sw_added_str = '\n'.join(sw_added_list)
with open(path, 'a') as f:
print(sw_added_str, file=f)
def load_sw_definition(path: str) -> list:
with open(path, 'r', encoding='utf-8') as f:
lines = f.read()
line_list = lines.split('\n')
line_list = [x for x in line_list if x != '']
return line_list
def remove_sw_from_text(wktstr: str, stopwords: list) -> str:
words_list = wktstr.split(' ')
words_list_swrm = [x for x in words_list if x not in stopwords]
swremoved_str = ' '.join(words_list_swrm)
return swremoved_str
def remove_sw_from_msgs(wktmsg_ser: pd.Series, stopwords: list) -> pd.Series:
swremved_msg_list = []
for wktstr in tqdm(wktmsg_ser, desc='remove stopwords...'):
removed_str = remove_sw_from_text(str(wktstr), stopwords)
swremved_msg_list.append(removed_str)
swremved_msg_ser = pd.Series(swremved_msg_list)
return swremved_msg_ser
def main(input_fname: str):
input_root = '../../data/030_processed'
output_root = input_root
# 1. load stop words
sw_def_fpath = 'stopwords.txt'
maybe_download(sw_def_fpath)
stopwords = load_sw_definition(sw_def_fpath)
# 2. load messages
msgs_fpath = input_root + '/' + input_fname
df_msgs = pd.read_csv(msgs_fpath)
# 3. remove stop words
ser_msg = df_msgs.wakati_msg
df_msgs.wakati_msg = remove_sw_from_msgs(ser_msg, stopwords)
# 4. save it
pin = Path(msgs_fpath)
msgs_ofpath = output_root + '/' + pin.stem + '_rmsw.csv'
df_msgs.to_csv(msgs_ofpath, index=False)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("input_fname", help="set input file name", type=str)
args = parser.parse_args()
input_fname = args.input_fname
# input file must been cleaned
if 'norm' not in input_fname:
print('input file name is invalid.: {0}'.format(input_fname))
print('input file name must include \'norm\'')
sys.exit(1)
main(input_fname)
Il existe de nombreux articles de commentaires intéressants. J'ai fait référence à cet article. TF-IDF | Qiita
Voici une explication approximative.
tf-idf est le produit de tf et idf. En d'autres termes ** Grand tf-idf ** = ** Apparaissant fréquemment dans un document ** & ** Ne figurant pas souvent dans d'autres documents ** = Important pour comprendre le ** contexte du document **
Le but de ce temps est de voir les caractéristiques des remarques pour une certaine période (1 semaine). Par conséquent, j'ai pensé qu'il devrait être possible de comprendre ** quelles sont les caractéristiques des remarques d'une certaine période (1 semaine) pour tous les postes jusqu'à présent **.
Donc,
J'ai calculé tf-idf comme.
Écrivez facilement le flux de processus
important_word_extraction.py
import pandas as pd
import json
from datetime import datetime, date, timedelta, timezone
from pathlib import Path
from sklearn.feature_extraction.text import TfidfVectorizer
JST = timezone(timedelta(hours=+9), 'JST')
#Regroupement des messages par période spécifiée
def group_msgs_by_term(df_msgs: pd.DataFrame, term: str) -> dict:
# set term
term_days = 8
if term == 'lm':
term_days = 31
print('group messages every {0} days'.format(term_days))
# analyze timestamp
now_in_sec = (datetime.now(JST) - datetime.fromtimestamp(0, JST)).total_seconds()
interval_days = timedelta(days=term_days)
interval_seconds = interval_days.total_seconds()
oldest_timestamp = df_msgs.min().timestamp
oldest_ts_in_sec = (datetime.fromtimestamp(oldest_timestamp, JST) - datetime.fromtimestamp(0, JST)).total_seconds()
loop_num = (abs(now_in_sec - oldest_ts_in_sec) / interval_seconds) + 1
# extract by term
dict_msgs_by_term = {}
df_tmp = df_msgs
now_tmp = now_in_sec
for i in range(int(loop_num)):
# make current term string
cur_term_s = 'term_ago_{0}'.format(str(i).zfill(3))
print(cur_term_s)
# current messages
df_msgs_cur = df_tmp.query('@now_tmp - timestamp < @interval_seconds')
df_msgs_other = df_tmp.query('@now_tmp - timestamp >= @interval_seconds')
# messages does not exist. break.
if df_msgs_cur.shape[0] == 0:
break
# add current messages to dict
dict_msgs_by_term[cur_term_s] = ' '.join(df_msgs_cur.wakati_msg.dropna().values.tolist())
# update temp value for next loop
now_tmp = now_tmp - interval_seconds
df_tmp = df_msgs_other
return dict_msgs_by_term
# tf-Extraire les mots importants et les renvoyer sous forme de dictionnaire en se référant au score idf
def extract_important_word_by_key(feature_names: list, bow_df: pd.DataFrame, uids: list) -> dict:
# >Regardez chaque ligne et extrayez les mots importants(tfidf Top X mots)
dict_important_words_by_user = {}
for uid, (i, scores) in zip(uids, bow_df.iterrows()):
#Créer une table de mots et de scores tfidf pour l'utilisateur
words_score_tbl = pd.DataFrame()
words_score_tbl['scores'] = scores
words_score_tbl['words'] = feature_names
#Trier par ordre décroissant par score tfidf
words_score_tbl = words_score_tbl.sort_values('scores', ascending=False)
words_score_tbl = words_score_tbl.reset_index()
# extract : tf-idf score > 0.001
important_words = words_score_tbl.query('scores > 0.001')
#Créer un dictionnaire pour l'utilisateur'uid0': {'w0': 0.9, 'w1': 0.87}
d = {}
for i, row in important_words.iterrows():
d[row.words] = row.scores
#Ajouter au tableau uniquement si le dictionnaire de l'utilisateur contient au moins un mot
if len(d.keys()) > 0:
dict_important_words_by_user[uid] = d
return dict_important_words_by_user
#Extraire les mots importants dans l'unité de période spécifiée
def extraction_by_term(input_root: str, output_root: str, term: str) -> dict:
# ---------------------------------------------
# 1. load messages (processed)
# ---------------------------------------------
print('load msgs (all of history and last term) ...')
msg_fpath = input_root + '/' + 'messages_cleaned_wakati_norm_rmsw.csv'
df_msgs_all = pd.read_csv(msg_fpath)
# ---------------------------------------------
# 2. group messages by term
# ---------------------------------------------
print('group messages by term and save it.')
msgs_grouped_by_term = group_msgs_by_term(df_msgs_all, term)
msg_grouped_fpath = input_root + '/' + 'messages_grouped_by_term.json'
with open(msg_grouped_fpath, 'w', encoding='utf-8') as f:
json.dump(msgs_grouped_by_term, f, ensure_ascii=False, indent=4)
# ---------------------------------------------
# 3.Tf pour tous les documents-calcul idf
# ---------------------------------------------
print('tfidf vectorizing ...')
# >Les mots de tous les documents sont des colonnes et le nombre de documents (=Une matrice est créée dans laquelle l'utilisateur) est la ligne. Tf pour chaque élément-Il y a une valeur idf
tfidf_vectorizer = TfidfVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
bow_vec = tfidf_vectorizer.fit_transform(msgs_grouped_by_term.values())
bow_array = bow_vec.toarray()
bow_df = pd.DataFrame(bow_array,
index=msgs_grouped_by_term.keys(),
columns=tfidf_vectorizer.get_feature_names())
# ---------------------------------------------
# 5. tf-Extraire des mots importants basés sur idf
# ---------------------------------------------
print('extract important words ...')
dict_word_score_by_term = extract_important_word_by_key(
tfidf_vectorizer.get_feature_names(),
bow_df, msgs_grouped_by_term.keys())
return dict_word_score_by_term
Les mots avec un score élevé sont grands et les mots avec un score faible sont petits. Diverses valeurs telles que "fréquence d'apparition" et "importance" peuvent être librement définies pour la partition.
Dépôt officiel: amueller / word_cloud: octocat:
Cette fois, je vais l'utiliser. Qu'est-ce que Homemade Rounded M +
Dans le chapitre précédent "8. Prétraitement: extraction de mots importants (tf-idf)", le fichier JSON suivant a été généré.
important_word_tfidf_by_term.json
{
"term_ago_000": {
"Les données": 0.890021,
"Jeu": 0.780122,
"article": 0.720025,
:
},
"term_ago_001": {
"Traduction": 0.680021,
"Les données": 0.620122,
"deepl": 0.580025,
:
},
:
}
Chargez ceci et créez une image Wordcloud.
Utilisez la méthode WordCloud.generate_from_frequencies ()
.
wordcloud_from_score.py
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import json
from pathlib import Path
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
from tqdm import tqdm
import sys
import argparse
def main(input_fname: str):
input_root = '../../data/031_features'
output_root = './wordcloud_by_user' if 'by_user' in input_fname else './wordcloud_by_term'
p = Path(output_root)
if p.exists() is False:
p.mkdir()
# -------------------------------------
# 1. load tf-idf score dictionary
# -------------------------------------
d_word_score_by_user = {}
tfidf_fpath = input_root + '/' + input_fname
with open(tfidf_fpath, 'r', encoding='utf-8') as f:
d_word_score_by_user = json.load(f)
# -------------------------------------
# 2. gen word cloud from score
# -------------------------------------
fontpath = './rounded-l-mplus-1c-regular.ttf'
for uname, d_word_score in tqdm(d_word_score_by_user.items(), desc='word cloud ...'):
# img file name is user.png
uname = str(uname).replace('/', '-')
out_img_fpath = output_root + '/' + uname + '.png'
# gen
wc = WordCloud(
background_color='white',
font_path=fontpath,
width=900, height=600,
collocations=False
)
wc.generate_from_frequencies(d_word_score)
wc.to_file(out_img_fpath)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("input_fname", help="set input file name", type=str)
args = parser.parse_args()
input_fname = args.input_fname
main(input_fname)
D'autres documents de référence (en grande quantité) sont résumés dans ici: octocat:.
Cette fois, nous utilisons les données de la communauté Slack appelée Data Learning Guild. La Data Learning Guild est une communauté en ligne de personnel d'analyse de données. Si vous êtes intéressé, veuillez vérifier ici.
Site Web officiel de Data Learning Guild
Calendrier de l'Avent Data Learning Guild 2019
Recommended Posts