Compare commits
30 Commits
synced/mas
...
charliepat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f728292461 | ||
|
|
95905cfc8b | ||
|
|
7df8436067 | ||
|
|
36b24ee933 | ||
|
|
6a3bfa26ee | ||
|
|
3a758f1fc8 | ||
|
|
46623927fe | ||
|
|
806cfc948f | ||
|
|
0fe120e4ab | ||
|
|
f20365c07e | ||
|
|
34e0a0a30d | ||
|
|
003a48aea5 | ||
|
|
37dd0ef55f | ||
|
|
ac06a8757a | ||
|
|
01a4c35358 | ||
|
|
628a70734b | ||
|
|
f0176d9f0d | ||
|
|
36cb0a5546 | ||
|
|
06430903f0 | ||
|
|
4dc949de5f | ||
|
|
140d1bdd17 | ||
|
|
554660275f | ||
|
|
b4dd9acbd8 | ||
| dbe4c87f8b | |||
|
|
3155600514 | ||
|
|
4e20dce188 | ||
|
|
56269deee3 | ||
|
|
e6294b5b90 | ||
|
|
a60747292e | ||
| db5879d6c9 |
@@ -1,79 +0,0 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
import numpy as np
|
||||
import pyarrow
|
||||
import pandas as pd
|
||||
import fire
|
||||
from itertools import islice
|
||||
from pathlib import Path
|
||||
from similarities_helper import cosine_similarities
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
conf = spark.sparkContext.getConf()
|
||||
|
||||
# outfile = '/gscratch/comdata/users/nathante/test_similarities_500.feather'; min_df = None; included_subreddits=None; similarity_threshold=0;
|
||||
def author_cosine_similarities(outfile, min_df = None, included_subreddits=None, similarity_threshold=0, topN=500):
|
||||
'''
|
||||
Compute similarities between subreddits based on tfi-idf vectors of author comments
|
||||
|
||||
included_subreddits : string
|
||||
Text file containing a list of subreddits to include (one per line) if included_subreddits is None then do the top 500 subreddits
|
||||
|
||||
similarity_threshold : double (default = 0)
|
||||
set > 0 for large numbers of subreddits to get an approximate solution using the DIMSUM algorithm
|
||||
https://stanford.edu/~rezab/papers/dimsum.pdf. If similarity_threshold=0 we get an exact solution using an O(N^2) algorithm.
|
||||
|
||||
min_df : int (default = 0.1 * (number of included_subreddits)
|
||||
exclude terms that appear in fewer than this number of documents.
|
||||
|
||||
outfile: string
|
||||
where to output csv and feather outputs
|
||||
'''
|
||||
|
||||
print(outfile)
|
||||
|
||||
tfidf = spark.read.parquet('/gscratch/comdata/users/nathante/subreddit_tfidf_authors.parquet')
|
||||
|
||||
if included_subreddits is None:
|
||||
included_subreddits = list(islice(open("/gscratch/comdata/users/nathante/cdsc-reddit/top_25000_subs_by_comments.txt"),topN))
|
||||
included_subreddits = {s.strip('\n') for s in included_subreddits}
|
||||
|
||||
else:
|
||||
included_subreddits = set(open(included_subreddits))
|
||||
|
||||
sim_dist, tfidf = cosine_similarities(tfidf, 'author', min_df, included_subreddits, similarity_threshold)
|
||||
|
||||
p = Path(outfile)
|
||||
|
||||
output_feather = Path(str(p).replace("".join(p.suffixes), ".feather"))
|
||||
output_csv = Path(str(p).replace("".join(p.suffixes), ".csv"))
|
||||
output_parquet = Path(str(p).replace("".join(p.suffixes), ".parquet"))
|
||||
sim_dist = sim_dist.entries.toDF()
|
||||
|
||||
sim_dist = sim_dist.repartition(1)
|
||||
sim_dist.write.parquet(str(output_parquet),mode='overwrite',compression='snappy')
|
||||
|
||||
|
||||
|
||||
#instead of toLocalMatrix() why not read as entries and put strait into numpy
|
||||
sim_entries = pd.read_parquet(output_parquet)
|
||||
|
||||
df = tfidf.select('subreddit','subreddit_id_new').distinct().toPandas()
|
||||
|
||||
spark.stop()
|
||||
df['subreddit_id_new'] = df['subreddit_id_new'] - 1
|
||||
df = df.sort_values('subreddit_id_new').reset_index(drop=True)
|
||||
df = df.set_index('subreddit_id_new')
|
||||
|
||||
similarities = sim_entries.join(df, on='i')
|
||||
similarities = similarities.rename(columns={'subreddit':"subreddit_i"})
|
||||
similarities = similarities.join(df, on='j')
|
||||
similarities = similarities.rename(columns={'subreddit':"subreddit_j"})
|
||||
|
||||
similarities.to_feather(output_feather)
|
||||
similarities.to_csv(output_csv)
|
||||
return similarities
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(author_cosine_similarities)
|
||||
74
bots/good_bad_bot.py
Normal file
74
bots/good_bad_bot.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from pyspark.sql.types import FloatType
|
||||
import zlib
|
||||
|
||||
def zlib_entropy_rate(s):
|
||||
sb = s.encode()
|
||||
if len(sb) == 0:
|
||||
return None
|
||||
else:
|
||||
return len(zlib.compress(s.encode(),level=6))/len(s.encode())
|
||||
|
||||
zlib_entropy_rate_udf = f.udf(zlib_entropy_rate,FloatType())
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_comments_by_author.parquet",compression='snappy')
|
||||
|
||||
df = df.withColumn("saidbot",f.lower(f.col("body")).like("%bot%"))
|
||||
|
||||
# df = df.filter(df.subreddit=='seattle')
|
||||
# df = df.cache()
|
||||
botreplies = df.filter(f.lower(df.body).rlike(".*[good|bad] bot.*"))
|
||||
botreplies = botreplies.select([f.col("parent_id").substr(4,100).alias("bot_comment_id"),f.lower(f.col("body")).alias("good_bad_bot"),f.col("link_id").alias("gbbb_link_id")])
|
||||
botreplies = botreplies.groupby(['bot_comment_id']).agg(f.count('good_bad_bot').alias("N_goodbad_votes"),
|
||||
f.sum((f.lower(f.col('good_bad_bot')).like('%good bot%').astype("double"))).alias("n_good_votes"),
|
||||
f.sum((f.lower(f.col('good_bad_bot')).like('%bad bot%').astype("double"))).alias("n_bad_votes"))
|
||||
|
||||
comments_by_author = df.select(['author','id','saidbot']).groupBy('author').agg(f.count('id').alias("N_comments"),
|
||||
f.mean(f.col('saidbot').astype("double")).alias("prop_saidbot"),
|
||||
f.sum(f.col('saidbot').astype("double")).alias("n_saidbot"))
|
||||
|
||||
# pd_comments_by_author = comments_by_author.toPandas()
|
||||
# pd_comments_by_author['frac'] = 500 / pd_comments_by_author['N_comments']
|
||||
# pd_comments_by_author.loc[pd_comments_by_author.frac > 1, 'frac'] = 1
|
||||
# fractions = pd_comments_by_author.loc[:,['author','frac']]
|
||||
# fractions = fractions.set_index('author').to_dict()['frac']
|
||||
|
||||
# sampled_author_comments = df.sampleBy("author",fractions).groupBy('author').agg(f.concat_ws(" ", f.collect_list('body')).alias('comments'))
|
||||
df = df.withColumn("randn",f.randn(seed=1968))
|
||||
|
||||
win = Window.partitionBy("author").orderBy("randn")
|
||||
|
||||
df = df.withColumn("randRank",f.rank().over(win))
|
||||
sampled_author_comments = df.filter(f.col("randRank") <= 1000)
|
||||
sampled_author_comments = sampled_author_comments.groupBy('author').agg(f.concat_ws(" ", f.collect_list('body')).alias('comments'))
|
||||
|
||||
author_entropy_rates = sampled_author_comments.select(['author',zlib_entropy_rate_udf(f.col('comments')).alias("entropy_rate")])
|
||||
|
||||
parents = df.join(botreplies, on=df.id==botreplies.bot_comment_id,how='right_outer')
|
||||
|
||||
win1 = Window.partitionBy("author")
|
||||
parents = parents.withColumn("first_bot_reply",f.min(f.col("CreatedAt")).over(win1))
|
||||
|
||||
first_bot_reply = parents.filter(f.col("first_bot_reply")==f.col("CreatedAt"))
|
||||
first_bot_reply = first_bot_reply.withColumnRenamed("CreatedAt","FB_CreatedAt")
|
||||
first_bot_reply = first_bot_reply.withColumnRenamed("id","FB_id")
|
||||
|
||||
comments_since_first_bot_reply = df.join(first_bot_reply,on = 'author',how='right_outer').filter(f.col("CreatedAt")>=f.col("first_bot_reply"))
|
||||
comments_since_first_bot_reply = comments_since_first_bot_reply.groupBy("author").agg(f.count("id").alias("N_comments_since_firstbot"))
|
||||
|
||||
bots = parents.groupby(['author']).agg(f.sum('N_goodbad_votes').alias("N_goodbad_votes"),
|
||||
f.sum(f.col('n_good_votes')).alias("n_good_votes"),
|
||||
f.sum(f.col('n_bad_votes')).alias("n_bad_votes"),
|
||||
f.count(f.col('author')).alias("N_bot_posts"))
|
||||
|
||||
bots = bots.join(comments_by_author,on="author",how='left_outer')
|
||||
bots = bots.join(comments_since_first_bot_reply,on="author",how='left_outer')
|
||||
bots = bots.join(author_entropy_rates,on='author',how='left_outer')
|
||||
|
||||
bots = bots.orderBy("N_goodbad_votes",ascending=False)
|
||||
bots = bots.repartition(1)
|
||||
bots.write.parquet("/gscratch/comdata/output/reddit_good_bad_bot.parquet",mode='overwrite')
|
||||
@@ -1,45 +0,0 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from sklearn.cluster import AffinityPropagation
|
||||
import fire
|
||||
|
||||
def affinity_clustering(similarities, output, damping=0.5, max_iter=100000, convergence_iter=30, preference_quantile=0.5, random_state=1968):
|
||||
'''
|
||||
similarities: feather file with a dataframe of similarity scores
|
||||
preference_quantile: parameter controlling how many clusters to make. higher values = more clusters. 0.85 is a good value with 3000 subreddits.
|
||||
'''
|
||||
|
||||
df = pd.read_feather(similarities)
|
||||
n = df.shape[0]
|
||||
mat = np.array(df.drop('subreddit',1))
|
||||
mat[range(n),range(n)] = 1
|
||||
|
||||
preference = np.quantile(mat,preference_quantile)
|
||||
|
||||
clustering = AffinityPropagation(damping=damping,
|
||||
max_iter=max_iter,
|
||||
convergence_iter=convergence_iter,
|
||||
copy=False,
|
||||
preference=preference,
|
||||
affinity='precomputed',
|
||||
random_state=random_state).fit(mat)
|
||||
|
||||
|
||||
print(f"clustering took {clustering.n_iter_} iterations")
|
||||
clusters = clustering.labels_
|
||||
|
||||
print(f"found {len(set(clusters))} clusters")
|
||||
|
||||
cluster_data = pd.DataFrame({'subreddit': df.subreddit,'cluster':clustering.labels_})
|
||||
|
||||
cluster_sizes = cluster_data.groupby("cluster").count()
|
||||
print(f"the largest cluster has {cluster_sizes.subreddit.max()} members")
|
||||
|
||||
print(f"the median cluster has {cluster_sizes.subreddit.median()} members")
|
||||
|
||||
print(f"{(cluster_sizes.subreddit==1).sum()} clusters have 1 member")
|
||||
|
||||
cluster_data.to_feather(output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire(affinity_clustering)
|
||||
76
clustering/Makefile
Normal file
76
clustering/Makefile
Normal file
@@ -0,0 +1,76 @@
|
||||
#srun_cdsc='srun -p comdata-int -A comdata --time=300:00:00 --time-min=00:15:00 --mem=100G --ntasks=1 --cpus-per-task=28'
|
||||
srun_singularity=source /gscratch/comdata/users/nathante/cdsc_reddit/bin/activate && srun_singularity.sh
|
||||
similarity_data=/gscratch/comdata/output/reddit_similarity
|
||||
clustering_data=/gscratch/comdata/output/reddit_clustering
|
||||
kmeans_selection_grid="--max_iter=3000 --n_init=[10] --n_clusters=[100,500,1000,1500,2000,2500,3000,2350,3500,3570,4000]"
|
||||
#selection_grid="--max_iter=3000 --convergence_iter=[15] --preference_quantile=[0.5] --damping=[0.99]"
|
||||
all:$(clustering_data)/subreddit_comment_authors_10k/kmeans/selection_data.csv $(clustering_data)/subreddit_comment_authors-tf_10k/kmeans/selection_data.csv $(clustering_data)/subreddit_comment_terms_10k/kmeans/selection_data.csv $(clustering_data)/subreddit_comment_terms_10k/affinity/selection_data.csv $(clustering_data)/subreddit_comment_authors_10k/affinity/selection_data.csv $(clustering_data)/subreddit_comment_authors-tf_10k/affinity/selection_data.csv
|
||||
# $(clustering_data)/subreddit_comment_authors_30k.feather/SUCCESS $(clustering_data)/subreddit_authors-tf_similarities_30k.feather/SUCCESS
|
||||
# $(clustering_data)/subreddit_comment_terms_30k.feather/SUCCESS
|
||||
|
||||
$(clustering_data)/subreddit_comment_authors_10k/kmeans/selection_data.csv:selection.py $(similarity_data)/subreddit_comment_authors_10k.feather clustering.py
|
||||
$(srun_singularity) python3 selection.py kmeans $(similarity_data)/subreddit_comment_authors_10k.feather $(clustering_data)/subreddit_comment_authors_10k/kmeans $(clustering_data)/subreddit_comment_authors_10k/kmeans/selection_data.csv $(kmeans_selection_grid)
|
||||
|
||||
$(clustering_data)/subreddit_comment_terms_10k/kmeans/selection_data.csv:selection.py $(similarity_data)/subreddit_comment_terms_10k.feather clustering.py
|
||||
$(srun_singularity) python3 selection.py kmeans $(similarity_data)/subreddit_comment_terms_10k.feather $(clustering_data)/subreddit_comment_terms_10k/kmeans $(clustering_data)/subreddit_comment_terms_10k/kmeans/selection_data.csv $(kmeans_selection_grid)
|
||||
|
||||
$(clustering_data)/subreddit_comment_authors-tf_10k/kmeans/selection_data.csv:clustering.py $(similarity_data)/subreddit_comment_authors-tf_10k.feather
|
||||
$(srun_singularity) python3 selection.py kmeans $(similarity_data)/subreddit_comment_authors-tf_10k.feather $(clustering_data)/subreddit_comment_authors-tf_10k/kmeans $(clustering_data)/subreddit_comment_authors-tf_10k/kmeans/selection_data.csv $(kmeans_selection_grid)
|
||||
|
||||
|
||||
affinity_selection_grid="--max_iter=3000 --convergence_iter=[15] --preference_quantile=[0.5] --damping=[0.99]"
|
||||
$(clustering_data)/subreddit_comment_authors_10k/affinity/selection_data.csv:selection.py $(similarity_data)/subreddit_comment_authors_10k.feather clustering.py
|
||||
$(srun_singularity) python3 selection.py affinity $(similarity_data)/subreddit_comment_authors_10k.feather $(clustering_data)/subreddit_comment_authors_10k/affinity $(clustering_data)/subreddit_comment_authors_10k/affinity/selection_data.csv $(affinity_selection_grid) -J 20
|
||||
|
||||
$(clustering_data)/subreddit_comment_terms_10k/affinity/selection_data.csv:selection.py $(similarity_data)/subreddit_comment_terms_10k.feather clustering.py
|
||||
$(srun_singularity) python3 selection.py affinity $(similarity_data)/subreddit_comment_terms_10k.feather $(clustering_data)/subreddit_comment_terms_10k/affinity $(clustering_data)/subreddit_comment_terms_10k/affinity/selection_data.csv $(affinity_selection_grid) -J 20
|
||||
|
||||
$(clustering_data)/subreddit_comment_authors-tf_10k/affinity/selection_data.csv:clustering.py $(similarity_data)/subreddit_comment_authors-tf_10k.feather
|
||||
$(srun_singularity) python3 selection.py affinity $(similarity_data)/subreddit_comment_authors-tf_10k.feather $(clustering_data)/subreddit_comment_authors-tf_10k/affinity $(clustering_data)/subreddit_comment_authors-tf_10k/affinity/selection_data.csv $(affinity_selection_grid) -J 20
|
||||
|
||||
clean:
|
||||
rm -f $(clustering_data)/subreddit_comment_authors-tf_10k/affinity/selection_data.csv
|
||||
rm -f $(clustering_data)/subreddit_comment_authors_10k/affinity/selection_data.csv
|
||||
rm -f $(clustering_data)/subreddit_comment_terms_10k/affinity/selection_data.csv
|
||||
rm -f $(clustering_data)/subreddit_comment_authors-tf_10k/kmeans/selection_data.csv
|
||||
rm -f $(clustering_data)/subreddit_comment_authors_10k/kmeans/selection_data.csv
|
||||
rm -f $(clustering_data)/subreddit_comment_terms_10k/kmeans/selection_data.csv
|
||||
|
||||
PHONY: clean
|
||||
|
||||
# $(clustering_data)/subreddit_comment_authors_30k.feather/SUCCESS:selection.py $(similarity_data)/subreddit_comment_authors_30k.feather clustering.py
|
||||
# $(srun_singularity) python3 selection.py $(similarity_data)/subreddit_comment_authors_30k.feather $(clustering_data)/subreddit_comment_authors_30k $(selection_grid) -J 10 && touch $(clustering_data)/subreddit_comment_authors_30k.feather/SUCCESS
|
||||
|
||||
# $(clustering_data)/subreddit_comment_terms_30k.feather/SUCCESS:selection.py $(similarity_data)/subreddit_comment_terms_30k.feather clustering.py
|
||||
# $(srun_singularity) python3 selection.py $(similarity_data)/subreddit_comment_terms_30k.feather $(clustering_data)/subreddit_comment_terms_30k $(selection_grid) -J 10 && touch $(clustering_data)/subreddit_comment_terms_30k.feather/SUCCESS
|
||||
|
||||
# $(clustering_data)/subreddit_authors-tf_similarities_30k.feather/SUCCESS:clustering.py $(similarity_data)/subreddit_comment_authors-tf_30k.feather
|
||||
# $(srun_singularity) python3 selection.py $(similarity_data)/subreddit_comment_authors-tf_30k.feather $(clustering_data)/subreddit_comment_authors-tf_30k $(selection_grid) -J 8 && touch $(clustering_data)/subreddit_authors-tf_similarities_30k.feather/SUCCESS
|
||||
|
||||
|
||||
# $(clustering_data)/subreddit_comment_authors_100k.feather:clustering.py $(similarity_data)/subreddit_comment_authors_100k.feather
|
||||
# $(srun_singularity) python3 clustering.py $(similarity_data)/subreddit_comment_authors_100k.feather $(clustering_data)/subreddit_comment_authors_100k.feather ---max_iter=400 --convergence_iter=15 --preference_quantile=0.85 --damping=0.85
|
||||
|
||||
# $(clustering_data)/comment_terms_100k.feather:clustering.py $(similarity_data)/subreddit_comment_terms_100k.feather
|
||||
# $(srun_singularity) python3 clustering.py $(similarity_data)/comment_terms_10000.feather $(clustering_data)/comment_terms_10000.feather ---max_iter=1000 --convergence_iter=15 --preference_quantile=0.9 --damping=0.5
|
||||
|
||||
# $(clustering_data)/subreddit_comment_author-tf_100k.feather:clustering.py $(similarity_data)/subreddit_comment_author-tf_100k.feather
|
||||
# $(srun_singularity) python3 clustering.py $(similarity_data)/subreddit_comment_author-tf_100k.parquet $(clustering_data)/subreddit_comment_author-tf_100k.feather ---max_iter=400 --convergence_iter=15 --preference_quantile=0.5 --damping=0.85
|
||||
|
||||
|
||||
# it's pretty difficult to get a result that isn't one huge megacluster. A sign that it's bullcrap
|
||||
# /gscratch/comdata/output/reddit_clustering/wang_similarity_10000.feather:clustering.py /gscratch/comdata/output/reddit_similarity/wang_similarity_10000.feather
|
||||
# ./clustering.py /gscratch/comdata/output/reddit_similarity/wang_similarity_10000.feather /gscratch/comdata/output/reddit_clustering/wang_similarity_10000.feather ---max_iter=400 --convergence_iter=15 --preference_quantile=0.9 --damping=0.85
|
||||
|
||||
# /gscratch/comdata/output/reddit_tsne/subreddit_author_tf_similarities_10000.feather:fit_tsne.py /gscratch/comdata/output/reddit_similarity/subreddit_author_tf_similarities_10000.parquet
|
||||
|
||||
# start_spark_and_run.sh 1 fit_tsne.py --similarities=/gscratch/comdata/output/reddit_similarity/subreddit_author_tf_similarities_10000.parquet --output=/gscratch/comdata/output/reddit_tsne/subreddit_author_tf_similarities_10000.feather
|
||||
|
||||
|
||||
# /gscratch/comdata/output/reddit_tsne/wang_similarity_10000.feather:fit_tsne.py /gscratch/comdata/output/reddit_similarity/wang_similarity_10000.feather
|
||||
|
||||
# python3 fit_tsne.py --similarities=/gscratch/comdata/output/reddit_similarity/wang_similarity_10000.feather --output=/gscratch/comdata/output/reddit_tsne/wang_similarity_10000.feather
|
||||
|
||||
# /gscratch/comdata/output/reddit_tsne/comment_authors_10000.feather:clustering.py /gscratch/comdata/output/reddit_similarity/comment_authors_10000.feather
|
||||
# # $srun_cdsc python3
|
||||
# start_spark_and_run.sh 1 fit_tsne.py --similarities=/gscratch/comdata/output/reddit_similarity/comment_authors_10000.feather --output=/gscratch/comdata/output/reddit_tsne/comment_authors_10000.feather
|
||||
BIN
clustering/affinity/subreddit_comment_authors_10000_a.feather
Normal file
BIN
clustering/affinity/subreddit_comment_authors_10000_a.feather
Normal file
Binary file not shown.
70
clustering/clustering.py
Executable file
70
clustering/clustering.py
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
# TODO: replace prints with logging.
|
||||
import sys
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from sklearn.cluster import AffinityPropagation, KMeans
|
||||
import fire
|
||||
from pathlib import Path
|
||||
from multiprocessing import cpu_count
|
||||
from dataclasses import dataclass
|
||||
from clustering_base import sim_to_dist, process_clustering_result, clustering_result, read_similarity_mat
|
||||
|
||||
def affinity_clustering(similarities, output, *args, **kwargs):
|
||||
subreddits, mat = read_similarity_mat(similarities)
|
||||
clustering = _affinity_clustering(mat, *args, **kwargs)
|
||||
cluster_data = process_clustering_result(clustering, subreddits)
|
||||
cluster_data['algorithm'] = 'affinity'
|
||||
return(cluster_data)
|
||||
|
||||
def _affinity_clustering(mat, subreddits, output, damping=0.9, max_iter=100000, convergence_iter=30, preference_quantile=0.5, random_state=1968, verbose=True):
|
||||
'''
|
||||
similarities: matrix of similarity scores
|
||||
preference_quantile: parameter controlling how many clusters to make. higher values = more clusters. 0.85 is a good value with 3000 subreddits.
|
||||
damping: parameter controlling how iterations are merged. Higher values make convergence faster and more dependable. 0.85 is a good value for the 10000 subreddits by author.
|
||||
'''
|
||||
print(f"damping:{damping}; convergenceIter:{convergence_iter}; preferenceQuantile:{preference_quantile}")
|
||||
|
||||
preference = np.quantile(mat,preference_quantile)
|
||||
|
||||
print(f"preference is {preference}")
|
||||
print("data loaded")
|
||||
sys.stdout.flush()
|
||||
clustering = AffinityPropagation(damping=damping,
|
||||
max_iter=max_iter,
|
||||
convergence_iter=convergence_iter,
|
||||
copy=False,
|
||||
preference=preference,
|
||||
affinity='precomputed',
|
||||
verbose=verbose,
|
||||
random_state=random_state).fit(mat)
|
||||
|
||||
cluster_data = process_clustering_result(clustering, subreddits)
|
||||
output = Path(output)
|
||||
output.parent.mkdir(parents=True,exist_ok=True)
|
||||
cluster_data.to_feather(output)
|
||||
print(f"saved {output}")
|
||||
return clustering
|
||||
|
||||
def kmeans_clustering(similarities, *args, **kwargs):
|
||||
subreddits, mat = read_similarity_mat(similarities)
|
||||
mat = sim_to_dist(mat)
|
||||
clustering = _kmeans_clustering(mat, *args, **kwargs)
|
||||
cluster_data = process_clustering_result(clustering, subreddits)
|
||||
return(cluster_data)
|
||||
|
||||
def _kmeans_clustering(mat, output, n_clusters, n_init=10, max_iter=100000, random_state=1968, verbose=True):
|
||||
|
||||
clustering = KMeans(n_clusters=n_clusters,
|
||||
n_init=n_init,
|
||||
max_iter=max_iter,
|
||||
random_state=random_state,
|
||||
verbose=verbose
|
||||
).fit(mat)
|
||||
|
||||
return clustering
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire(affinity_clustering)
|
||||
49
clustering/clustering_base.py
Normal file
49
clustering/clustering_base.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from dataclasses import dataclass
|
||||
|
||||
def sim_to_dist(mat):
|
||||
dist = 1-mat
|
||||
dist[dist < 0] = 0
|
||||
np.fill_diagonal(dist,0)
|
||||
return dist
|
||||
|
||||
def process_clustering_result(clustering, subreddits):
|
||||
|
||||
if hasattr(clustering,'n_iter_'):
|
||||
print(f"clustering took {clustering.n_iter_} iterations")
|
||||
|
||||
clusters = clustering.labels_
|
||||
|
||||
print(f"found {len(set(clusters))} clusters")
|
||||
|
||||
cluster_data = pd.DataFrame({'subreddit': subreddits,'cluster':clustering.labels_})
|
||||
|
||||
cluster_sizes = cluster_data.groupby("cluster").count().reset_index()
|
||||
print(f"the largest cluster has {cluster_sizes.loc[cluster_sizes.cluster!=-1].subreddit.max()} members")
|
||||
|
||||
print(f"the median cluster has {cluster_sizes.subreddit.median()} members")
|
||||
|
||||
print(f"{(cluster_sizes.subreddit==1).sum()} clusters have 1 member")
|
||||
|
||||
print(f"{(cluster_sizes.loc[cluster_sizes.cluster==-1,['subreddit']])} subreddits are in cluster -1",flush=True)
|
||||
|
||||
return cluster_data
|
||||
|
||||
|
||||
@dataclass
|
||||
class clustering_result:
|
||||
outpath:Path
|
||||
max_iter:int
|
||||
silhouette_score:float
|
||||
alt_silhouette_score:float
|
||||
name:str
|
||||
n_clusters:int
|
||||
|
||||
def read_similarity_mat(similarities, use_threads=True):
|
||||
df = pd.read_feather(similarities, use_threads=use_threads)
|
||||
mat = np.array(df.drop('_subreddit',1))
|
||||
n = mat.shape[0]
|
||||
mat[range(n),range(n)] = 1
|
||||
return (df._subreddit,mat)
|
||||
@@ -5,7 +5,7 @@ from numpy import random
|
||||
import numpy as np
|
||||
from sklearn.manifold import TSNE
|
||||
|
||||
similarities = "term_similarities_10000.feather"
|
||||
similarities = "/gscratch/comdata/output/reddit_similarity/subreddit_author_tf_similarities_10000.parquet"
|
||||
|
||||
def fit_tsne(similarities, output, learning_rate=750, perplexity=50, n_iter=10000, early_exaggeration=20):
|
||||
'''
|
||||
172
clustering/hdbscan_clustering.py
Normal file
172
clustering/hdbscan_clustering.py
Normal file
@@ -0,0 +1,172 @@
|
||||
from clustering_base import sim_to_dist, process_clustering_result, clustering_result, read_similarity_mat
|
||||
from dataclasses import dataclass
|
||||
import hdbscan
|
||||
from sklearn.neighbors import NearestNeighbors
|
||||
import plotnine as pn
|
||||
import numpy as np
|
||||
from itertools import product, starmap
|
||||
import pandas as pd
|
||||
from sklearn.metrics import silhouette_score, silhouette_samples
|
||||
from pathlib import Path
|
||||
from multiprocessing import Pool, cpu_count
|
||||
import fire
|
||||
from pyarrow.feather import write_feather
|
||||
|
||||
def test_select_hdbscan_clustering():
|
||||
select_hdbscan_clustering("/gscratch/comdata/output/reddit_similarity/subreddit_comment_authors-tf_30k_LSI",
|
||||
"test_hdbscan_author30k",
|
||||
min_cluster_sizes=[2],
|
||||
min_samples=[1,2],
|
||||
cluster_selection_epsilons=[0,0.05,0.1,0.15],
|
||||
cluster_selection_methods=['eom','leaf'],
|
||||
lsi_dimensions='all')
|
||||
inpath = "/gscratch/comdata/output/reddit_similarity/subreddit_comment_authors-tf_30k_LSI"
|
||||
outpath = "test_hdbscan";
|
||||
min_cluster_sizes=[2,3,4];
|
||||
min_samples=[1,2,3];
|
||||
cluster_selection_epsilons=[0,0.1,0.3,0.5];
|
||||
cluster_selection_methods=['eom'];
|
||||
lsi_dimensions='all'
|
||||
|
||||
@dataclass
|
||||
class hdbscan_clustering_result(clustering_result):
|
||||
min_cluster_size:int
|
||||
min_samples:int
|
||||
cluster_selection_epsilon:float
|
||||
cluster_selection_method:str
|
||||
lsi_dimensions:int
|
||||
n_isolates:int
|
||||
silhouette_samples:str
|
||||
|
||||
def select_hdbscan_clustering(inpath,
|
||||
outpath,
|
||||
outfile=None,
|
||||
min_cluster_sizes=[2],
|
||||
min_samples=[1],
|
||||
cluster_selection_epsilons=[0],
|
||||
cluster_selection_methods=['eom'],
|
||||
lsi_dimensions='all'
|
||||
):
|
||||
|
||||
inpath = Path(inpath)
|
||||
outpath = Path(outpath)
|
||||
outpath.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
if lsi_dimensions == 'all':
|
||||
lsi_paths = list(inpath.glob("*"))
|
||||
|
||||
else:
|
||||
lsi_paths = [inpath / (dim + '.feather') for dim in lsi_dimensions]
|
||||
|
||||
lsi_nums = [p.stem for p in lsi_paths]
|
||||
grid = list(product(lsi_nums,
|
||||
min_cluster_sizes,
|
||||
min_samples,
|
||||
cluster_selection_epsilons,
|
||||
cluster_selection_methods))
|
||||
|
||||
# fix the output file names
|
||||
names = list(map(lambda t:'_'.join(map(str,t)),grid))
|
||||
|
||||
grid = [(inpath/(str(t[0])+'.feather'),outpath/(name + '.feather'), t[0], name) + t[1:] for t, name in zip(grid, names)]
|
||||
|
||||
with Pool(int(cpu_count()/4)) as pool:
|
||||
mods = starmap(hdbscan_clustering, grid)
|
||||
|
||||
res = pd.DataFrame(mods)
|
||||
if outfile is None:
|
||||
outfile = outpath / "selection_data.csv"
|
||||
|
||||
res.to_csv(outfile)
|
||||
|
||||
def hdbscan_clustering(similarities, output, lsi_dim, name, min_cluster_size=2, min_samples=1, cluster_selection_epsilon=0, cluster_selection_method='eom'):
|
||||
subreddits, mat = read_similarity_mat(similarities)
|
||||
mat = sim_to_dist(mat)
|
||||
clustering = _hdbscan_clustering(mat,
|
||||
min_cluster_size=min_cluster_size,
|
||||
min_samples=min_samples,
|
||||
cluster_selection_epsilon=cluster_selection_epsilon,
|
||||
cluster_selection_method=cluster_selection_method,
|
||||
metric='precomputed',
|
||||
core_dist_n_jobs=cpu_count()
|
||||
)
|
||||
|
||||
cluster_data = process_clustering_result(clustering, subreddits)
|
||||
isolates = clustering.labels_ == -1
|
||||
scoremat = mat[~isolates][:,~isolates]
|
||||
score = silhouette_score(scoremat, clustering.labels_[~isolates], metric='precomputed')
|
||||
cluster_data.to_feather(output)
|
||||
|
||||
silhouette_samp = silhouette_samples(mat, clustering.labels_, metric='precomputed')
|
||||
silhouette_samp = pd.DataFrame({'subreddit':subreddits,'score':silhouette_samp})
|
||||
silsampout = output.parent / ("silhouette_samples" + output.name)
|
||||
silhouette_samp.to_feather(silsampout)
|
||||
|
||||
result = hdbscan_clustering_result(outpath=output,
|
||||
max_iter=None,
|
||||
silhouette_samples=silsampout,
|
||||
silhouette_score=score,
|
||||
alt_silhouette_score=score,
|
||||
name=name,
|
||||
min_cluster_size=min_cluster_size,
|
||||
min_samples=min_samples,
|
||||
cluster_selection_epsilon=cluster_selection_epsilon,
|
||||
cluster_selection_method=cluster_selection_method,
|
||||
lsi_dimensions=lsi_dim,
|
||||
n_isolates=isolates.sum(),
|
||||
n_clusters=len(set(clustering.labels_))
|
||||
)
|
||||
|
||||
|
||||
|
||||
return(result)
|
||||
|
||||
# for all runs we should try cluster_selection_epsilon = None
|
||||
# for terms we should try cluster_selection_epsilon around 0.56-0.66
|
||||
# for authors we should try cluster_selection_epsilon around 0.98-0.99
|
||||
def _hdbscan_clustering(mat, *args, **kwargs):
|
||||
print(f"running hdbscan clustering. args:{args}. kwargs:{kwargs}")
|
||||
|
||||
print(mat)
|
||||
clusterer = hdbscan.HDBSCAN(*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
clustering = clusterer.fit(mat.astype('double'))
|
||||
|
||||
return(clustering)
|
||||
|
||||
def KNN_distances_plot(mat,outname,k=2):
|
||||
nbrs = NearestNeighbors(n_neighbors=k,algorithm='auto',metric='precomputed').fit(mat)
|
||||
distances, indices = nbrs.kneighbors(mat)
|
||||
d2 = distances[:,-1]
|
||||
df = pd.DataFrame({'dist':d2})
|
||||
df = df.sort_values("dist",ascending=False)
|
||||
df['idx'] = np.arange(0,d2.shape[0]) + 1
|
||||
p = pn.qplot(x='idx',y='dist',data=df,geom='line') + pn.scales.scale_y_continuous(minor_breaks = np.arange(0,50)/50,
|
||||
breaks = np.arange(0,10)/10)
|
||||
p.save(outname,width=16,height=10)
|
||||
|
||||
def make_KNN_plots():
|
||||
similarities = "/gscratch/comdata/output/reddit_similarity/subreddit_comment_terms_10k.feather"
|
||||
subreddits, mat = read_similarity_mat(similarities)
|
||||
mat = sim_to_dist(mat)
|
||||
|
||||
KNN_distances_plot(mat,k=2,outname='terms_knn_dist2.png')
|
||||
|
||||
similarities = "/gscratch/comdata/output/reddit_similarity/subreddit_comment_authors_10k.feather"
|
||||
subreddits, mat = read_similarity_mat(similarities)
|
||||
mat = sim_to_dist(mat)
|
||||
KNN_distances_plot(mat,k=2,outname='authors_knn_dist2.png')
|
||||
|
||||
similarities = "/gscratch/comdata/output/reddit_similarity/subreddit_comment_authors-tf_10k.feather"
|
||||
subreddits, mat = read_similarity_mat(similarities)
|
||||
mat = sim_to_dist(mat)
|
||||
KNN_distances_plot(mat,k=2,outname='authors-tf_knn_dist2.png')
|
||||
|
||||
if __name__ == "__main__":
|
||||
df = pd.read_csv("test_hdbscan/selection_data.csv")
|
||||
test_select_hdbscan_clustering()
|
||||
check_clusters = pd.read_feather("test_hdbscan/500_2_2_0.1_eom.feather")
|
||||
silscores = pd.read_feather("test_hdbscan/silhouette_samples500_2_2_0.1_eom.feather")
|
||||
c = check_clusters.merge(silscores,on='subreddit')# fire.Fire(select_hdbscan_clustering)
|
||||
132
clustering/select_affinity.py
Normal file
132
clustering/select_affinity.py
Normal file
@@ -0,0 +1,132 @@
|
||||
from sklearn.metrics import silhouette_score
|
||||
from sklearn.cluster import AffinityPropagation
|
||||
from functools import partial
|
||||
from dataclasses import dataclass
|
||||
from clustering import _affinity_clustering, read_similarity_mat, sim_to_dist, process_clustering_result, clustering_result
|
||||
from multiprocessing import Pool, cpu_count, Array, Process
|
||||
from pathlib import Path
|
||||
from itertools import product, starmap
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import fire
|
||||
import sys
|
||||
|
||||
# silhouette is the only one that doesn't need the feature matrix. So it's probably the only one that's worth trying.
|
||||
@dataclass
|
||||
class affinity_clustering_result(clustering_result):
|
||||
damping:float
|
||||
convergence_iter:int
|
||||
preference_quantile:float
|
||||
|
||||
def do_affinity_clustering(damping, convergence_iter, preference_quantile, name, mat, subreddits, max_iter, outdir:Path, random_state, verbose, alt_mat, overwrite=False):
|
||||
if name is None:
|
||||
name = f"damping-{damping}_convergenceIter-{convergence_iter}_preferenceQuantile-{preference_quantile}"
|
||||
print(name)
|
||||
sys.stdout.flush()
|
||||
outpath = outdir / (str(name) + ".feather")
|
||||
outpath.parent.mkdir(parents=True,exist_ok=True)
|
||||
print(outpath)
|
||||
clustering = _affinity_clustering(mat, outpath, damping, max_iter, convergence_iter, preference_quantile, random_state, verbose)
|
||||
cluster_data = process_clustering_result(clustering, subreddits)
|
||||
mat = sim_to_dist(clustering.affinity_matrix_)
|
||||
|
||||
try:
|
||||
score = silhouette_score(mat, clustering.labels_, metric='precomputed')
|
||||
except ValueError:
|
||||
score = None
|
||||
|
||||
if alt_mat is not None:
|
||||
alt_distances = sim_to_dist(alt_mat)
|
||||
try:
|
||||
alt_score = silhouette_score(alt_mat, clustering.labels_, metric='precomputed')
|
||||
except ValueError:
|
||||
alt_score = None
|
||||
|
||||
res = affinity_clustering_result(outpath=outpath,
|
||||
damping=damping,
|
||||
max_iter=max_iter,
|
||||
convergence_iter=convergence_iter,
|
||||
preference_quantile=preference_quantile,
|
||||
silhouette_score=score,
|
||||
alt_silhouette_score=score,
|
||||
name=str(name))
|
||||
|
||||
return res
|
||||
|
||||
def do_affinity_clustering(damping, convergence_iter, preference_quantile, name, mat, subreddits, max_iter, outdir:Path, random_state, verbose, alt_mat, overwrite=False):
|
||||
if name is None:
|
||||
name = f"damping-{damping}_convergenceIter-{convergence_iter}_preferenceQuantile-{preference_quantile}"
|
||||
print(name)
|
||||
sys.stdout.flush()
|
||||
outpath = outdir / (str(name) + ".feather")
|
||||
outpath.parent.mkdir(parents=True,exist_ok=True)
|
||||
print(outpath)
|
||||
clustering = _affinity_clustering(mat, subreddits, outpath, damping, max_iter, convergence_iter, preference_quantile, random_state, verbose)
|
||||
mat = sim_to_dist(clustering.affinity_matrix_)
|
||||
|
||||
try:
|
||||
score = silhouette_score(mat, clustering.labels_, metric='precomputed')
|
||||
except ValueError:
|
||||
score = None
|
||||
|
||||
if alt_mat is not None:
|
||||
alt_distances = sim_to_dist(alt_mat)
|
||||
try:
|
||||
alt_score = silhouette_score(alt_mat, clustering.labels_, metric='precomputed')
|
||||
except ValueError:
|
||||
alt_score = None
|
||||
|
||||
res = clustering_result(outpath=outpath,
|
||||
damping=damping,
|
||||
max_iter=max_iter,
|
||||
convergence_iter=convergence_iter,
|
||||
preference_quantile=preference_quantile,
|
||||
silhouette_score=score,
|
||||
alt_silhouette_score=score,
|
||||
name=str(name))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
# alt similiarities is for checking the silhouette coefficient of an alternative measure of similarity (e.g., topic similarities for user clustering).
|
||||
|
||||
def select_affinity_clustering(similarities, outdir, outinfo, damping=[0.9], max_iter=100000, convergence_iter=[30], preference_quantile=[0.5], random_state=1968, verbose=True, alt_similarities=None, J=None):
|
||||
|
||||
damping = list(map(float,damping))
|
||||
convergence_iter = convergence_iter = list(map(int,convergence_iter))
|
||||
preference_quantile = list(map(float,preference_quantile))
|
||||
|
||||
if type(outdir) is str:
|
||||
outdir = Path(outdir)
|
||||
|
||||
outdir.mkdir(parents=True,exist_ok=True)
|
||||
|
||||
subreddits, mat = read_similarity_mat(similarities,use_threads=True)
|
||||
|
||||
if alt_similarities is not None:
|
||||
alt_mat = read_similarity_mat(alt_similarities,use_threads=True)
|
||||
else:
|
||||
alt_mat = None
|
||||
|
||||
if J is None:
|
||||
J = cpu_count()
|
||||
pool = Pool(J)
|
||||
|
||||
# get list of tuples: the combinations of hyperparameters
|
||||
hyper_grid = product(damping, convergence_iter, preference_quantile)
|
||||
hyper_grid = (t + (str(i),) for i, t in enumerate(hyper_grid))
|
||||
|
||||
_do_clustering = partial(do_affinity_clustering, mat=mat, subreddits=subreddits, outdir=outdir, max_iter=max_iter, random_state=random_state, verbose=verbose, alt_mat=alt_mat)
|
||||
|
||||
# similarities = Array('d', mat)
|
||||
# call pool.starmap
|
||||
print("running clustering selection")
|
||||
clustering_data = pool.starmap(_do_clustering, hyper_grid)
|
||||
clustering_data = pd.DataFrame(list(clustering_data))
|
||||
clustering_data.to_csv(outinfo)
|
||||
|
||||
|
||||
return clustering_data
|
||||
|
||||
if __name__ == "__main__":
|
||||
x = fire.Fire(select_affinity_clustering)
|
||||
92
clustering/select_kmeans.py
Normal file
92
clustering/select_kmeans.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from sklearn.metrics import silhouette_score
|
||||
from sklearn.cluster import AffinityPropagation
|
||||
from functools import partial
|
||||
from clustering import _kmeans_clustering, read_similarity_mat, sim_to_dist, process_clustering_result, clustering_result
|
||||
from dataclasses import dataclass
|
||||
from multiprocessing import Pool, cpu_count, Array, Process
|
||||
from pathlib import Path
|
||||
from itertools import product, starmap
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import fire
|
||||
import sys
|
||||
|
||||
@dataclass
|
||||
class kmeans_clustering_result(clustering_result):
|
||||
n_clusters:int
|
||||
n_init:int
|
||||
|
||||
|
||||
# silhouette is the only one that doesn't need the feature matrix. So it's probably the only one that's worth trying.
|
||||
|
||||
def do_clustering(n_clusters, n_init, name, mat, subreddits, max_iter, outdir:Path, random_state, verbose, alt_mat, overwrite=False):
|
||||
if name is None:
|
||||
name = f"damping-{damping}_convergenceIter-{convergence_iter}_preferenceQuantile-{preference_quantile}"
|
||||
print(name)
|
||||
sys.stdout.flush()
|
||||
outpath = outdir / (str(name) + ".feather")
|
||||
print(outpath)
|
||||
mat = sim_to_dist(mat)
|
||||
clustering = _kmeans_clustering(mat, outpath, n_clusters, n_init, max_iter, random_state, verbose)
|
||||
|
||||
outpath.parent.mkdir(parents=True,exist_ok=True)
|
||||
cluster_data.to_feather(outpath)
|
||||
cluster_data = process_clustering_result(clustering, subreddits)
|
||||
|
||||
try:
|
||||
score = silhouette_score(mat, clustering.labels_, metric='precomputed')
|
||||
except ValueError:
|
||||
score = None
|
||||
|
||||
if alt_mat is not None:
|
||||
alt_distances = sim_to_dist(alt_mat)
|
||||
try:
|
||||
alt_score = silhouette_score(alt_mat, clustering.labels_, metric='precomputed')
|
||||
except ValueError:
|
||||
alt_score = None
|
||||
|
||||
res = kmeans_clustering_result(outpath=outpath,
|
||||
max_iter=max_iter,
|
||||
n_clusters=n_clusters,
|
||||
n_init = n_init,
|
||||
silhouette_score=score,
|
||||
alt_silhouette_score=score,
|
||||
name=str(name))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
# alt similiarities is for checking the silhouette coefficient of an alternative measure of similarity (e.g., topic similarities for user clustering).
|
||||
def select_kmeans_clustering(similarities, outdir, outinfo, n_clusters=[1000], max_iter=100000, n_init=10, random_state=1968, verbose=True, alt_similarities=None):
|
||||
|
||||
n_clusters = list(map(int,n_clusters))
|
||||
n_init = list(map(int,n_init))
|
||||
|
||||
if type(outdir) is str:
|
||||
outdir = Path(outdir)
|
||||
|
||||
outdir.mkdir(parents=True,exist_ok=True)
|
||||
|
||||
subreddits, mat = read_similarity_mat(similarities,use_threads=True)
|
||||
|
||||
if alt_similarities is not None:
|
||||
alt_mat = read_similarity_mat(alt_similarities,use_threads=True)
|
||||
else:
|
||||
alt_mat = None
|
||||
|
||||
# get list of tuples: the combinations of hyperparameters
|
||||
hyper_grid = product(n_clusters, n_init)
|
||||
hyper_grid = (t + (str(i),) for i, t in enumerate(hyper_grid))
|
||||
|
||||
_do_clustering = partial(do_clustering, mat=mat, subreddits=subreddits, outdir=outdir, max_iter=max_iter, random_state=random_state, verbose=verbose, alt_mat=alt_mat)
|
||||
|
||||
# call starmap
|
||||
print("running clustering selection")
|
||||
clustering_data = starmap(_do_clustering, hyper_grid)
|
||||
clustering_data = pd.DataFrame(list(clustering_data))
|
||||
clustering_data.to_csv(outinfo)
|
||||
|
||||
return clustering_data
|
||||
|
||||
if __name__ == "__main__":
|
||||
x = fire.Fire(select_kmeans_clustering)
|
||||
7
clustering/selection.py
Normal file
7
clustering/selection.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import fire
|
||||
from select_affinity import select_affinity_clustering
|
||||
from select_kmeans import select_kmeans_clustering
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire({"kmeans":select_kmeans_clustering,
|
||||
"affinity":select_affinity_clustering})
|
||||
4
datasets/job_script.sh
Executable file
4
datasets/job_script.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
start_spark_cluster.sh
|
||||
spark-submit --master spark://$(hostname):18899 weekly_cosine_similarities.py term --outfile=/gscratch/comdata/users/nathante/subreddit_term_similarity_weekly_5000.parquet --topN=5000
|
||||
stop-all.sh
|
||||
10
density/Makefile
Normal file
10
density/Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
all: /gscratch/comdata/output/reddit_density/comment_terms_10000.feather /gscratch/comdata/output/reddit_density/comment_authors_10000.feather /gscratch/comdata/output/reddit_density/subreddit_author_tf_similarities_10000.feather
|
||||
|
||||
/gscratch/comdata/output/reddit_density/comment_terms_10000.feather:overlap_density.py /gscratch/comdata/output/reddit_similarity/comment_terms_10000.feather /gscratch/comdata/output/reddit_similarity/comment_terms_10000.feather
|
||||
start_spark_and_run.sh 1 overlap_density.py terms --inpath="/gscratch/comdata/output/reddit_similarity/comment_terms_10000.feather" --outpath="/gscratch/comdata/output/reddit_density/comment_terms_10000.feather" --agg=pd.DataFrame.sum
|
||||
|
||||
/gscratch/comdata/output/reddit_density/comment_authors_10000.feather:overlap_density.py /gscratch/comdata/output/reddit_similarity/comment_authors_10000.feather
|
||||
start_spark_and_run.sh 1 overlap_density.py authors --inpath="/gscratch/comdata/output/reddit_similarity/comment_authors_10000.feather" --outpath="/gscratch/comdata/output/reddit_density/comment_authors_10000.feather" --agg=pd.DataFrame.sum
|
||||
|
||||
/gscratch/comdata/output/reddit_density/subreddit_author_tf_similarities_10000.feather: overlap_density.py /gscratch/comdata/output/reddit_similarity/subreddit_author_tf_similarities_10000.parquet
|
||||
start_spark_and_run.sh 1 overlap_density.py authors --inpath="/gscratch/comdata/output/reddit_similarity/subreddit_author_tf_similarities_10000.parquet" --outpath="/gscratch/comdata/output/reddit_density/subreddit_author_tf_similarities_10000.feather" --agg=pd.DataFrame.sum
|
||||
4
density/job_script.sh
Executable file
4
density/job_script.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
start_spark_cluster.sh
|
||||
spark-submit --master spark://$(hostname):18899 overlap_density.py authors --inpath=/gscratch/comdata/output/reddit_similarity/comment_authors_10000.feather --outpath=/gscratch/comdata/output/reddit_density/comment_authors_10000.feather --agg=pd.DataFrame.sum
|
||||
stop-all.sh
|
||||
76
density/overlap_density.py
Normal file
76
density/overlap_density.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import pandas as pd
|
||||
from pandas.core.groupby import DataFrameGroupBy as GroupBy
|
||||
import fire
|
||||
import numpy as np
|
||||
import sys
|
||||
sys.path.append("..")
|
||||
sys.path.append("../similarities")
|
||||
from similarities.similarities_helper import reindex_tfidf, reindex_tfidf_time_interval
|
||||
|
||||
# this is the mean of the ratio of the overlap to the focal size.
|
||||
# mean shared membership per focal community member
|
||||
# the input is the author tf-idf matrix
|
||||
|
||||
def overlap_density(inpath, outpath, agg = pd.DataFrame.sum):
|
||||
df = pd.read_feather(inpath)
|
||||
df = df.drop('subreddit',1)
|
||||
np.fill_diagonal(df.values,0)
|
||||
df = agg(df, 0).reset_index()
|
||||
df = df.rename({0:'overlap_density'},axis='columns')
|
||||
df.to_feather(outpath)
|
||||
return df
|
||||
|
||||
def overlap_density_weekly(inpath, outpath, agg = GroupBy.sum):
|
||||
df = pd.read_parquet(inpath)
|
||||
# exclude the diagonal
|
||||
df = df.loc[df.subreddit != df.variable]
|
||||
res = agg(df.groupby(['subreddit','week'])).reset_index()
|
||||
res.to_feather(outpath)
|
||||
return res
|
||||
|
||||
|
||||
# inpath="/gscratch/comdata/output/reddit_similarity/tfidf/comment_authors.parquet";
|
||||
# min_df=1;
|
||||
# included_subreddits=None;
|
||||
# topN=10000;
|
||||
# outpath="/gscratch/comdata/output/reddit_density/wang_overlaps_10000.feather"
|
||||
|
||||
# to_date=2019-10-28
|
||||
|
||||
|
||||
def author_overlap_density(inpath="/gscratch/comdata/output/reddit_similarity/comment_authors_10000.feather",
|
||||
outpath="/gscratch/comdata/output/reddit_density/comment_authors_10000.feather", agg=pd.DataFrame.sum):
|
||||
if type(agg) == str:
|
||||
agg = eval(agg)
|
||||
|
||||
overlap_density(inpath, outpath, agg)
|
||||
|
||||
def term_overlap_density(inpath="/gscratch/comdata/output/reddit_similarity/comment_terms_10000.feather",
|
||||
outpath="/gscratch/comdata/output/reddit_density/comment_term_similarity_10000.feather", agg=pd.DataFrame.sum):
|
||||
|
||||
if type(agg) == str:
|
||||
agg = eval(agg)
|
||||
|
||||
overlap_density(inpath, outpath, agg)
|
||||
|
||||
def author_overlap_density_weekly(inpath="/gscratch/comdata/output/reddit_similarity/subreddit_authors_10000_weekly.parquet",
|
||||
outpath="/gscratch/comdata/output/reddit_density/comment_authors_10000_weekly.feather", agg=GroupBy.sum):
|
||||
if type(agg) == str:
|
||||
agg = eval(agg)
|
||||
|
||||
overlap_density_weekly(inpath, outpath, agg)
|
||||
|
||||
def term_overlap_density_weekly(inpath="/gscratch/comdata/output/reddit_similarity/comment_terms_10000_weekly.parquet",
|
||||
outpath="/gscratch/comdata/output/reddit_density/comment_terms_10000_weekly.parquet", agg=GroupBy.sum):
|
||||
if type(agg) == str:
|
||||
agg = eval(agg)
|
||||
|
||||
overlap_density_weekly(inpath, outpath, agg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire({'authors':author_overlap_density,
|
||||
'terms':term_overlap_density,
|
||||
'author_weekly':author_overlap_density_weekly,
|
||||
'term_weekly':term_overlap_density_weekly})
|
||||
|
||||
0
ngrams/#ngrams_helper.py#
Normal file
0
ngrams/#ngrams_helper.py#
Normal file
26
ngrams/checkpoint_parallelsql.sbatch
Normal file
26
ngrams/checkpoint_parallelsql.sbatch
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
## parallel_sql_job.sh
|
||||
#SBATCH --job-name=tf_subreddit_comments
|
||||
## Allocation Definition
|
||||
#SBATCH --account=comdata-ckpt
|
||||
#SBATCH --partition=ckpt
|
||||
## Resources
|
||||
## Nodes. This should always be 1 for parallel-sql.
|
||||
#SBATCH --nodes=1
|
||||
## Walltime (12 hours)
|
||||
#SBATCH --time=12:00:00
|
||||
## Memory per node
|
||||
#SBATCH --mem=32G
|
||||
#SBATCH --cpus-per-task=4
|
||||
#SBATCH --ntasks=1
|
||||
#SBATCH -D /gscratch/comdata/users/nathante/cdsc-reddit
|
||||
source ./bin/activate
|
||||
module load parallel_sql
|
||||
echo $(which perl)
|
||||
conda list pyarrow
|
||||
which python3
|
||||
#Put here commands to load other modules (e.g. matlab etc.)
|
||||
#Below command means that parallel_sql will get tasks from the database
|
||||
#and run them on the node (in parallel). So a 16 core node will have
|
||||
#16 tasks running at one time.
|
||||
parallel-sql --sql -a parallel --exit-on-term --jobs 4
|
||||
@@ -7,7 +7,6 @@ from itertools import groupby, islice, chain
|
||||
import fire
|
||||
from collections import Counter
|
||||
import os
|
||||
import datetime
|
||||
import re
|
||||
from nltk import wordpunct_tokenize, MWETokenizer, sent_tokenize
|
||||
from nltk.corpus import stopwords
|
||||
@@ -31,8 +30,8 @@ def weekly_tf(partition, mwe_pass = 'first'):
|
||||
ngram_output = partition.replace("parquet","txt")
|
||||
|
||||
if mwe_pass == 'first':
|
||||
if os.path.exists(f"/gscratch/comdata/users/nathante/reddit_comment_ngrams_10p_sample/{ngram_output}"):
|
||||
os.remove(f"/gscratch/comdata/users/nathante/reddit_comment_ngrams_10p_sample/{ngram_output}")
|
||||
if os.path.exists(f"/gscratch/comdata/output/reddit_ngrams/comment_ngrams_10p_sample/{ngram_output}"):
|
||||
os.remove(f"/gscratch/comdata/output/reddit_ngrams/comment_ngrams_10p_sample/{ngram_output}")
|
||||
|
||||
batches = dataset.to_batches(columns=['CreatedAt','subreddit','body','author'])
|
||||
|
||||
@@ -67,7 +66,7 @@ def weekly_tf(partition, mwe_pass = 'first'):
|
||||
subreddit_weeks = groupby(rows, lambda r: (r.subreddit, r.week))
|
||||
|
||||
if mwe_pass != 'first':
|
||||
mwe_dataset = pd.read_feather(f'/gscratch/comdata/users/nathante/reddit_multiword_expressions.feather')
|
||||
mwe_dataset = pd.read_feather(f'/gscratch/comdata/output/reddit_ngrams/multiword_expressions.feather')
|
||||
mwe_dataset = mwe_dataset.sort_values(['phrasePWMI'],ascending=False)
|
||||
mwe_phrases = list(mwe_dataset.phrase)
|
||||
mwe_phrases = [tuple(s.split(' ')) for s in mwe_phrases]
|
||||
@@ -88,7 +87,6 @@ def weekly_tf(partition, mwe_pass = 'first'):
|
||||
new_sentence.append(new_token)
|
||||
return new_sentence
|
||||
|
||||
|
||||
stopWords = set(stopwords.words('english'))
|
||||
|
||||
# we follow the approach described in datta, phelan, adar 2017
|
||||
@@ -121,7 +119,7 @@ def weekly_tf(partition, mwe_pass = 'first'):
|
||||
for sentence in sentences:
|
||||
if random() <= 0.1:
|
||||
grams = list(chain(*map(lambda i : ngrams(sentence,i),range(4))))
|
||||
with open(f'/gscratch/comdata/users/nathante/reddit_comment_ngrams_10p_sample/{ngram_output}','a') as gram_file:
|
||||
with open(f'/gscratch/comdata/output/reddit_ngrams/comment_ngrams_10p_sample/{ngram_output}','a') as gram_file:
|
||||
for ng in grams:
|
||||
gram_file.write(' '.join(ng) + '\n')
|
||||
for token in sentence:
|
||||
@@ -156,7 +154,7 @@ def weekly_tf(partition, mwe_pass = 'first'):
|
||||
|
||||
outchunksize = 10000
|
||||
|
||||
with pq.ParquetWriter(f"/gscratch/comdata/users/nathante/reddit_tfidf_test.parquet_temp/{partition}",schema=schema,compression='snappy',flavor='spark') as writer, pq.ParquetWriter(f"/gscratch/comdata/users/nathante/reddit_tfidf_test_authors.parquet_temp/{partition}",schema=author_schema,compression='snappy',flavor='spark') as author_writer:
|
||||
with pq.ParquetWriter(f"/gscratch/comdata/output/reddit_ngrams/comment_terms.parquet/{partition}",schema=schema,compression='snappy',flavor='spark') as writer, pq.ParquetWriter(f"/gscratch/comdata/output/reddit_ngrams/comment_authors.parquet/{partition}",schema=author_schema,compression='snappy',flavor='spark') as author_writer:
|
||||
|
||||
while True:
|
||||
|
||||
21
old/#tfidf_authors.py#
Normal file
21
old/#tfidf_authors.py#
Normal file
@@ -0,0 +1,21 @@
|
||||
from pyspark.sql import SparkSession
|
||||
from similarities_helper import build_tfidf_dataset
|
||||
import pandas as pd
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_ngrams/comment_authors.parquet")
|
||||
|
||||
include_subs = pd.read_csv("/gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments.csv")
|
||||
|
||||
include_subs = set(include_subs.loc[include_subs.comments_rank <= 25000]['subreddit'])
|
||||
|
||||
# remove [deleted] and AutoModerator (TODO remove other bots)
|
||||
df = df.filter(df.author != '[deleted]')
|
||||
df = df.filter(df.author != 'AutoModerator')
|
||||
|
||||
df = build_tfidf_dataset(df, include_subs, 'author')
|
||||
|
||||
df.write.parquet('/gscratch/comdata/output/reddit_similarity/tfidf/subreddit_comment_authors.parquet',mode='overwrite',compression='snappy')
|
||||
|
||||
spark.stop()
|
||||
27
old/#tfidf_comments_weekly.py#
Normal file
27
old/#tfidf_comments_weekly.py#
Normal file
@@ -0,0 +1,27 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from similarities_helper import build_weekly_tfidf_dataset
|
||||
import pandas as pd
|
||||
|
||||
|
||||
## TODO:need to exclude automoderator / bot posts.
|
||||
## TODO:need to exclude better handle hyperlinks.
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_ngrams/comment_terms.parquet")
|
||||
|
||||
include_subs = pd.read_csv("/gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments.csv")
|
||||
|
||||
include_subs = set(include_subs.loc[include_subs.comments_rank <= 25000]['subreddit'])
|
||||
|
||||
# remove [deleted] and AutoModerator (TODO remove other bots)
|
||||
# df = df.filter(df.author != '[deleted]')
|
||||
# df = df.filter(df.author != 'AutoModerator')
|
||||
|
||||
df = build_weekly_tfidf_dataset(df, include_subs, 'term')
|
||||
|
||||
|
||||
df.write.parquet('/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_terms.parquet', mode='overwrite', compression='snappy')
|
||||
spark.stop()
|
||||
|
||||
106
old/author_cosine_similarity.py
Normal file
106
old/author_cosine_similarity.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
import numpy as np
|
||||
import pyarrow
|
||||
import pandas as pd
|
||||
import fire
|
||||
from itertools import islice
|
||||
from pathlib import Path
|
||||
from similarities_helper import *
|
||||
|
||||
#tfidf = spark.read.parquet('/gscratch/comdata/output/reddit_similarity/tfidf_weekly/subreddit_terms.parquet')
|
||||
def cosine_similarities_weekly(tfidf_path, outfile, term_colname, min_df = None, included_subreddits = None, topN = 500):
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
conf = spark.sparkContext.getConf()
|
||||
print(outfile)
|
||||
tfidf = spark.read.parquet(tfidf_path)
|
||||
|
||||
if included_subreddits is None:
|
||||
included_subreddits = select_topN_subreddits(topN)
|
||||
|
||||
else:
|
||||
included_subreddits = set(open(included_subreddits))
|
||||
|
||||
print("creating temporary parquet with matrix indicies")
|
||||
tempdir = prep_tfidf_entries_weekly(tfidf, term_colname, min_df, included_subreddits)
|
||||
|
||||
tfidf = spark.read.parquet(tempdir.name)
|
||||
|
||||
# the ids can change each week.
|
||||
subreddit_names = tfidf.select(['subreddit','subreddit_id_new','week']).distinct().toPandas()
|
||||
subreddit_names = subreddit_names.sort_values("subreddit_id_new")
|
||||
subreddit_names['subreddit_id_new'] = subreddit_names['subreddit_id_new'] - 1
|
||||
spark.stop()
|
||||
|
||||
weeks = list(subreddit_names.week.drop_duplicates())
|
||||
for week in weeks:
|
||||
print("loading matrix")
|
||||
mat = read_tfidf_matrix_weekly(tempdir.name, term_colname, week)
|
||||
print('computing similarities')
|
||||
sims = column_similarities(mat)
|
||||
del mat
|
||||
|
||||
names = subreddit_names.loc[subreddit_names.week==week]
|
||||
|
||||
sims = sims.rename({i:sr for i, sr in enumerate(names.subreddit.values)},axis=1)
|
||||
sims['subreddit'] = names.subreddit.values
|
||||
write_weekly_similarities(outfile, sims, week)
|
||||
|
||||
|
||||
|
||||
def cosine_similarities(outfile, min_df = None, included_subreddits=None, topN=500):
|
||||
'''
|
||||
Compute similarities between subreddits based on tfi-idf vectors of author comments
|
||||
|
||||
included_subreddits : string
|
||||
Text file containing a list of subreddits to include (one per line) if included_subreddits is None then do the top 500 subreddits
|
||||
|
||||
min_df : int (default = 0.1 * (number of included_subreddits)
|
||||
exclude terms that appear in fewer than this number of documents.
|
||||
|
||||
outfile: string
|
||||
where to output csv and feather outputs
|
||||
'''
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
conf = spark.sparkContext.getConf()
|
||||
print(outfile)
|
||||
|
||||
tfidf = spark.read.parquet('/gscratch/comdata/output/reddit_similarity/tfidf/subreddit_comment_authors.parquet')
|
||||
|
||||
if included_subreddits is None:
|
||||
included_subreddits = select_topN_subreddits(topN)
|
||||
|
||||
else:
|
||||
included_subreddits = set(open(included_subreddits))
|
||||
|
||||
print("creating temporary parquet with matrix indicies")
|
||||
tempdir = prep_tfidf_entries(tfidf, 'author', min_df, included_subreddits)
|
||||
tfidf = spark.read.parquet(tempdir.name)
|
||||
subreddit_names = tfidf.select(['subreddit','subreddit_id_new']).distinct().toPandas()
|
||||
subreddit_names = subreddit_names.sort_values("subreddit_id_new")
|
||||
subreddit_names['subreddit_id_new'] = subreddit_names['subreddit_id_new'] - 1
|
||||
spark.stop()
|
||||
|
||||
print("loading matrix")
|
||||
mat = read_tfidf_matrix(tempdir.name,'author')
|
||||
print('computing similarities')
|
||||
sims = column_similarities(mat)
|
||||
del mat
|
||||
|
||||
sims = pd.DataFrame(sims.todense())
|
||||
sims = sims.rename({i:sr for i, sr in enumerate(subreddit_names.subreddit.values)},axis=1)
|
||||
sims['subreddit'] = subreddit_names.subreddit.values
|
||||
|
||||
p = Path(outfile)
|
||||
|
||||
output_feather = Path(str(p).replace("".join(p.suffixes), ".feather"))
|
||||
output_csv = Path(str(p).replace("".join(p.suffixes), ".csv"))
|
||||
output_parquet = Path(str(p).replace("".join(p.suffixes), ".parquet"))
|
||||
|
||||
sims.to_feather(outfile)
|
||||
tempdir.cleanup()
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(author_cosine_similarities)
|
||||
61
old/term_cosine_similarity.py
Normal file
61
old/term_cosine_similarity.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from pyspark.mllib.linalg.distributed import RowMatrix, CoordinateMatrix
|
||||
import numpy as np
|
||||
import pyarrow
|
||||
import pandas as pd
|
||||
import fire
|
||||
from itertools import islice
|
||||
from pathlib import Path
|
||||
from similarities_helper import prep_tfidf_entries, read_tfidf_matrix, column_similarities, select_topN
|
||||
import scipy
|
||||
|
||||
# outfile='test_similarities_500.feather';
|
||||
# min_df = None;
|
||||
# included_subreddits=None; topN=100; exclude_phrases=True;
|
||||
def term_cosine_similarities(outfile, min_df = None, included_subreddits=None, topN=500, exclude_phrases=False):
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
conf = spark.sparkContext.getConf()
|
||||
print(outfile)
|
||||
print(exclude_phrases)
|
||||
|
||||
tfidf = spark.read.parquet('/gscratch/comdata/output/reddit_similarity/tfidf/subreddit_terms.parquet')
|
||||
|
||||
if included_subreddits is None:
|
||||
included_subreddits = select_topN_subreddits(topN)
|
||||
else:
|
||||
included_subreddits = set(open(included_subreddits))
|
||||
|
||||
if exclude_phrases == True:
|
||||
tfidf = tfidf.filter(~f.col(term).contains("_"))
|
||||
|
||||
print("creating temporary parquet with matrix indicies")
|
||||
tempdir = prep_tfidf_entries(tfidf, 'term', min_df, included_subreddits)
|
||||
tfidf = spark.read.parquet(tempdir.name)
|
||||
subreddit_names = tfidf.select(['subreddit','subreddit_id_new']).distinct().toPandas()
|
||||
subreddit_names = subreddit_names.sort_values("subreddit_id_new")
|
||||
subreddit_names['subreddit_id_new'] = subreddit_names['subreddit_id_new'] - 1
|
||||
spark.stop()
|
||||
|
||||
print("loading matrix")
|
||||
mat = read_tfidf_matrix(tempdir.name,'term')
|
||||
print('computing similarities')
|
||||
sims = column_similarities(mat)
|
||||
del mat
|
||||
|
||||
sims = pd.DataFrame(sims.todense())
|
||||
sims = sims.rename({i:sr for i, sr in enumerate(subreddit_names.subreddit.values)},axis=1)
|
||||
sims['subreddit'] = subreddit_names.subreddit.values
|
||||
|
||||
p = Path(outfile)
|
||||
|
||||
output_feather = Path(str(p).replace("".join(p.suffixes), ".feather"))
|
||||
output_csv = Path(str(p).replace("".join(p.suffixes), ".csv"))
|
||||
output_parquet = Path(str(p).replace("".join(p.suffixes), ".parquet"))
|
||||
|
||||
sims.to_feather(outfile)
|
||||
tempdir.cleanup()
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(term_cosine_similarities)
|
||||
21
old/tfidf_authors.py
Normal file
21
old/tfidf_authors.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from pyspark.sql import SparkSession
|
||||
from similarities_helper import build_tfidf_dataset
|
||||
import pandas as pd
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_ngrams/comment_authors.parquet")
|
||||
|
||||
include_subs = pd.read_csv("/gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments.csv")
|
||||
|
||||
include_subs = set(include_subs.loc[include_subs.comments_rank <= 25000]['subreddit'])
|
||||
|
||||
# remove [deleted] and AutoModerator (TODO remove other bots)
|
||||
df = df.filter(df.author != '[deleted]')
|
||||
df = df.filter(df.author != 'AutoModerator')
|
||||
|
||||
df = build_tfidf_dataset(df, include_subs, 'author')
|
||||
|
||||
df.write.parquet('/gscratch/comdata/output/reddit_similarity/tfidf/subreddit_comment_authors.parquet',mode='overwrite',compression='snappy')
|
||||
|
||||
spark.stop()
|
||||
21
old/tfidf_authors_weekly.py
Normal file
21
old/tfidf_authors_weekly.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from pyspark.sql import SparkSession
|
||||
from similarities_helper import build_weekly_tfidf_dataset
|
||||
import pandas as pd
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_ngrams/comment_authors.parquet")
|
||||
|
||||
include_subs = pd.read_csv("/gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments.csv")
|
||||
|
||||
include_subs = set(include_subs.loc[include_subs.comments_rank <= 25000]['subreddit'])
|
||||
|
||||
# remove [deleted] and AutoModerator (TODO remove other bots)
|
||||
df = df.filter(df.author != '[deleted]')
|
||||
df = df.filter(df.author != 'AutoModerator')
|
||||
|
||||
df = build_weekly_tfidf_dataset(df, include_subs, 'author')
|
||||
|
||||
df.write.parquet('/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_authors.parquet', mode='overwrite', compression='snappy')
|
||||
|
||||
spark.stop()
|
||||
18
old/tfidf_comments.py
Normal file
18
old/tfidf_comments.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from similarities_helper import build_tfidf_dataset
|
||||
|
||||
## TODO:need to exclude automoderator / bot posts.
|
||||
## TODO:need to exclude better handle hyperlinks.
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_ngrams/comment_terms.parquet")
|
||||
|
||||
include_subs = pd.read_csv("/gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments.csv")
|
||||
include_subs = set(include_subs.loc[include_subs.comments_rank <= 25000]['subreddit'])
|
||||
|
||||
df = build_tfidf_dataset(df, include_subs, 'term')
|
||||
|
||||
df.write.parquet('/gscratch/comdata/output/reddit_similarity/reddit_similarity/subreddit_terms.parquet',mode='overwrite',compression='snappy')
|
||||
spark.stop()
|
||||
27
old/tfidf_comments_weekly.py
Normal file
27
old/tfidf_comments_weekly.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from similarities_helper import build_weekly_tfidf_dataset
|
||||
import pandas as pd
|
||||
|
||||
|
||||
## TODO:need to exclude automoderator / bot posts.
|
||||
## TODO:need to exclude better handle hyperlinks.
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_ngrams/comment_terms.parquet")
|
||||
|
||||
include_subs = pd.read_csv("/gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments.csv")
|
||||
|
||||
include_subs = set(include_subs.loc[include_subs.comments_rank <= 25000]['subreddit'])
|
||||
|
||||
# remove [deleted] and AutoModerator (TODO remove other bots)
|
||||
# df = df.filter(df.author != '[deleted]')
|
||||
# df = df.filter(df.author != 'AutoModerator')
|
||||
|
||||
df = build_weekly_tfidf_dataset(df, include_subs, 'term')
|
||||
|
||||
|
||||
df.write.parquet('/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_terms.parquet', mode='overwrite', compression='snappy')
|
||||
spark.stop()
|
||||
|
||||
24
similarities/#tfidf_weekly.py#
Normal file
24
similarities/#tfidf_weekly.py#
Normal file
@@ -0,0 +1,24 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from similarities_helper import build_weekly_tfidf_dataset
|
||||
import pandas as pd
|
||||
|
||||
def tfidf_weekly(inpath, outpath, topN, term_colname, exclude):
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_ngrams/comment_terms.parquet")
|
||||
|
||||
include_subs = pd.read_csv("/gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments.csv")
|
||||
|
||||
include_subs = set(include_subs.loc[include_subs.comments_rank <= 25000]['subreddit'])
|
||||
|
||||
# remove [deleted] and AutoModerator (TODO remove other bots)
|
||||
# df = df.filter(df.author != '[deleted]')
|
||||
# df = df.filter(df.author != 'AutoModerator')
|
||||
|
||||
df = build_weekly_tfidf_dataset(df, include_subs, 'term')
|
||||
|
||||
|
||||
df.write.parquet('/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_terms.parquet', mode='overwrite', compression='snappy')
|
||||
spark.stop()
|
||||
130
similarities/Makefile
Normal file
130
similarities/Makefile
Normal file
@@ -0,0 +1,130 @@
|
||||
#all: /gscratch/comdata/output/reddit_similarity/tfidf/comment_terms_130k.parquet /gscratch/comdata/output/reddit_similarity/tfidf/comment_authors_130k.parquet /gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_terms_130k.parquet /gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_authors_130k.parquet
|
||||
srun_singularity=source /gscratch/comdata/users/nathante/cdsc_reddit/bin/activate && srun_singularity.sh
|
||||
srun_singularity_huge=source /gscratch/comdata/users/nathante/cdsc_reddit/bin/activate && srun_singularity_huge.sh
|
||||
base_data=/gscratch/comdata/output/
|
||||
similarity_data=${base_data}/reddit_similarity
|
||||
tfidf_data=${similarity_data}/tfidf
|
||||
tfidf_weekly_data=${similarity_data}/tfidf_weekly
|
||||
similarity_weekly_data=${similarity_data}/weekly
|
||||
lsi_components=[10,50,100,200,300,400,500,600,700,850,1000,1500]
|
||||
|
||||
lsi_similarities: ${similarity_data}/subreddit_comment_terms_10k_LSI ${similarity_data}/subreddit_comment_authors-tf_10k_LSI ${similarity_data}/subreddit_comment_authors_10k_LSI ${similarity_data}/subreddit_comment_terms_30k_LSI ${similarity_data}/subreddit_comment_authors-tf_30k_LSI ${similarity_data}/subreddit_comment_authors_30k_LSI
|
||||
|
||||
all: ${tfidf_data}/comment_terms_100k.parquet ${tfidf_data}/comment_terms_30k.parquet ${tfidf_data}/comment_terms_10k.parquet ${tfidf_data}/comment_authors_100k.parquet ${tfidf_data}/comment_authors_30k.parquet ${tfidf_data}/comment_authors_10k.parquet ${similarity_data}/subreddit_comment_authors_30k.feather ${similarity_data}/subreddit_comment_authors_10k.feather ${similarity_data}/subreddit_comment_terms_10k.feather ${similarity_data}/subreddit_comment_terms_30k.feather ${similarity_data}/subreddit_comment_authors-tf_30k.feather ${similarity_data}/subreddit_comment_authors-tf_10k.feather ${similarity_data}/subreddit_comment_terms_100k.feather ${similarity_data}/subreddit_comment_authors_100k.feather ${similarity_data}/subreddit_comment_authors-tf_100k.feather ${similarity_weekly_data}/comment_terms.parquet
|
||||
|
||||
#${tfidf_weekly_data}/comment_terms_100k.parquet ${tfidf_weekly_data}/comment_authors_100k.parquet ${tfidf_weekly_data}/comment_terms_30k.parquet ${tfidf_weekly_data}/comment_authors_30k.parquet ${similarity_weekly_data}/comment_terms_100k.parquet ${similarity_weekly_data}/comment_authors_100k.parquet ${similarity_weekly_data}/comment_terms_30k.parquet ${similarity_weekly_data}/comment_authors_30k.parquet
|
||||
|
||||
# /gscratch/comdata/output/reddit_similarity/subreddit_comment_authors_130k.parquet /gscratch/comdata/output/reddit_similarity/subreddit_comment_authors_130k.parquet /gscratch/comdata/output/reddit_similarity/subreddit_author_tf_similarities_130k.parquet /gscratch/comdata/output/reddit_similarity/subreddit_comment_terms_130k.parquet /gscratch/comdata/output/reddit_similarity/comment_terms_weekly_130k.parquet
|
||||
|
||||
# all: /gscratch/comdata/output/reddit_similarity/subreddit_comment_terms_25000.parquet /gscratch/comdata/output/reddit_similarity/subreddit_comment_authors_25000.parquet /gscratch/comdata/output/reddit_similarity/subreddit_comment_authors_10000.parquet /gscratch/comdata/output/reddit_similarity/comment_terms_10000_weekly.parquet
|
||||
|
||||
${similarity_weekly_data}/comment_terms.parquet: weekly_cosine_similarities.py similarities_helper.py /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv ${tfidf_weekly_data}/comment_terms.parquet
|
||||
${srun_singularity} python3 weekly_cosine_similarities.py terms --topN=10000 --outfile=${similarity_weekly_data}/comment_terms.parquet
|
||||
|
||||
${similarity_data}/subreddit_comment_terms_10k.feather: ${tfidf_data}/comment_terms_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 cosine_similarities.py term --outfile=${similarity_data}/subreddit_comment_terms_10k.feather --topN=10000
|
||||
|
||||
${similarity_data}/subreddit_comment_terms_10k_LSI: ${tfidf_data}/comment_terms_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 lsi_similarities.py term --outfile=${similarity_data}/subreddit_comment_terms_10k_LSI --topN=10000 --n_components=${lsi_components} --min_df=200
|
||||
|
||||
${similarity_data}/subreddit_comment_terms_30k_LSI: ${tfidf_data}/comment_terms_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 lsi_similarities.py term --outfile=${similarity_data}/subreddit_comment_terms_30k_LSI --topN=30000 --n_components=${lsi_components} --min_df=200
|
||||
|
||||
${similarity_data}/subreddit_comment_terms_30k.feather: ${tfidf_data}/comment_terms_30k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 cosine_similarities.py term --outfile=${similarity_data}/subreddit_comment_terms_30k.feather --topN=30000
|
||||
|
||||
${similarity_data}/subreddit_comment_authors_30k.feather: ${tfidf_data}/comment_authors_30k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 cosine_similarities.py author --outfile=${similarity_data}/subreddit_comment_authors_30k.feather --topN=30000
|
||||
|
||||
${similarity_data}/subreddit_comment_authors_10k.feather: ${tfidf_data}/comment_authors_10k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 cosine_similarities.py author --outfile=${similarity_data}/subreddit_comment_authors_10k.feather --topN=10000
|
||||
|
||||
${similarity_data}/subreddit_comment_authors_10k_LSI: ${tfidf_data}/comment_authors_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 lsi_similarities.py author --outfile=${similarity_data}/subreddit_comment_authors_10k_LSI --topN=10000 --n_components=${lsi_components} --min_df=2
|
||||
|
||||
${similarity_data}/subreddit_comment_authors_30k_LSI: ${tfidf_data}/comment_authors_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 lsi_similarities.py author --outfile=${similarity_data}/subreddit_comment_authors_30k_LSI --topN=30000 --n_components=${lsi_components} --min_df=2
|
||||
|
||||
${similarity_data}/subreddit_comment_authors-tf_30k.feather: ${tfidf_data}/comment_authors_30k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 cosine_similarities.py author-tf --outfile=${similarity_data}/subreddit_comment_authors-tf_30k.feather --topN=30000
|
||||
|
||||
${similarity_data}/subreddit_comment_authors-tf_10k.feather: ${tfidf_data}/comment_authors_10k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 cosine_similarities.py author-tf --outfile=${similarity_data}/subreddit_comment_authors-tf_10k.feather --topN=10000
|
||||
|
||||
${similarity_data}/subreddit_comment_authors-tf_10k_LSI: ${tfidf_data}/comment_authors_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 lsi_similarities.py author-tf --outfile=${similarity_data}/subreddit_comment_authors-tf_10k_LSI --topN=10000 --n_components=${lsi_components} --min_df=2
|
||||
|
||||
${similarity_data}/subreddit_comment_authors-tf_30k_LSI: ${tfidf_data}/comment_authors_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 lsi_similarities.py author-tf --outfile=${similarity_data}/subreddit_comment_authors-tf_30k_LSI --topN=30000 --n_components=${lsi_components} --min_df=2
|
||||
|
||||
${similarity_data}/subreddit_comment_terms_100k.feather: ${tfidf_data}/comment_terms_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 cosine_similarities.py term --outfile=${similarity_data}/subreddit_comment_terms_100k.feather --topN=100000
|
||||
|
||||
${similarity_data}/subreddit_comment_authors_100k.feather: ${tfidf_data}/comment_authors_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 cosine_similarities.py author --outfile=${similarity_data}/subreddit_comment_authors_100k.feather --topN=100000
|
||||
|
||||
${similarity_data}/subreddit_comment_authors-tf_100k.feather: ${tfidf_data}/comment_authors_100k.parquet similarities_helper.py
|
||||
${srun_singularity} python3 cosine_similarities.py author-tf --outfile=${similarity_data}/subreddit_comment_authors-tf_100k.feather --topN=100000
|
||||
|
||||
${tfidf_data}/comment_terms_100k.feather/: /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv
|
||||
mkdir -p ${tfidf_data}/
|
||||
start_spark_and_run.sh 4 tfidf.py terms --topN=100000 --outpath=${tfidf_data}/comment_terms_100k.feather
|
||||
|
||||
${tfidf_data}/comment_terms_30k.feather: /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv
|
||||
mkdir -p ${tfidf_data}/
|
||||
start_spark_and_run.sh 4 tfidf.py terms --topN=30000 --outpath=${tfidf_data}/comment_terms_30k.feather
|
||||
|
||||
${tfidf_data}/comment_terms_10k.feather: /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv
|
||||
mkdir -p ${tfidf_data}/
|
||||
start_spark_and_run.sh 4 tfidf.py terms --topN=10000 --outpath=${tfidf_data}/comment_terms_10k.feather
|
||||
|
||||
${tfidf_data}/comment_authors_100k.feather: /gscratch/comdata/output/reddit_ngrams/comment_authors.parquet ${similarity_data}/subreddits_by_num_comments.csv
|
||||
mkdir -p ${tfidf_data}/
|
||||
start_spark_and_run.sh 4 tfidf.py authors --topN=100000 --outpath=${tfidf_data}/comment_authors_100k.feather
|
||||
|
||||
${tfidf_data}/comment_authors_10k.parquet: /gscratch/comdata/output/reddit_ngrams/comment_authors.parquet ${similarity_data}/subreddits_by_num_comments.csv
|
||||
mkdir -p ${tfidf_data}/
|
||||
start_spark_and_run.sh 4 tfidf.py authors --topN=10000 --outpath=${tfidf_data}/comment_authors_10k.parquet
|
||||
|
||||
${tfidf_data}/comment_authors_30k.parquet: /gscratch/comdata/output/reddit_ngrams/comment_authors.parquet ${similarity_data}/subreddits_by_num_comments.csv
|
||||
mkdir -p ${tfidf_data}/
|
||||
start_spark_and_run.sh 4 tfidf.py authors --topN=30000 --outpath=${tfidf_data}/comment_authors_30k.parquet
|
||||
|
||||
${tfidf_data}/tfidf_weekly/comment_terms_100k.parquet: /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv
|
||||
start_spark_and_run.sh 4 tfidf.py terms_weekly --topN=100000 --outpath=${similarity_data}/tfidf_weekly/comment_authors_100k.parquet
|
||||
|
||||
${tfidf_data}/tfidf_weekly/comment_authors_100k.parquet: /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_ppnum_comments.csv
|
||||
start_spark_and_run.sh 4 tfidf.py authors_weekly --topN=100000 --outpath=${tfidf_weekly_data}/comment_authors_100k.parquet
|
||||
|
||||
${tfidf_weekly_data}/comment_terms_30k.parquet: /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv
|
||||
start_spark_and_run.sh 4 tfidf.py terms_weekly --topN=30000 --outpath=${tfidf_weekly_data}/comment_authors_30k.parquet
|
||||
|
||||
${tfidf_weekly_data}/comment_authors_30k.parquet: /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv
|
||||
start_spark_and_run.sh 4 tfidf.py authors_weekly --topN=30000 --outpath=${tfidf_weekly_data}/comment_authors_30k.parquet
|
||||
|
||||
${similarity_weekly_data}/comment_terms_100k.parquet: weekly_cosine_similarities.py similarities_helper.py ${tfidf_weekly_data}/comment_terms_100k.parquet
|
||||
${srun_singularity} python3 weekly_cosine_similarities.py terms --topN=100000 --outfile=${similarity_weekly_data}/comment_authors_100k.parquet
|
||||
|
||||
${similarity_weekly_data}/comment_authors_100k.parquet: weekly_cosine_similarities.py similarities_helper.py /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv ${tfidf_weekly_data}/comment_authors_100k.parquet
|
||||
${srun_singularity} python3 weekly_cosine_similarities.py authors --topN=100000 --outfile=${similarity_weekly_data}/comment_authors_100k.parquet
|
||||
|
||||
${similarity_weekly_data}/comment_terms_30k.parquet: weekly_cosine_similarities.py similarities_helper.py /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv ${tfidf_weekly_data}/comment_terms_30k.parquet
|
||||
${srun_singularity} python3 weekly_cosine_similarities.py terms --topN=30000 --outfile=${similarity_weekly_data}/comment_authors_30k.parquet
|
||||
|
||||
${similarity_weekly_data}/comment_authors_30k.parquet: weekly_cosine_similarities.py similarities_helper.py /gscratch/comdata/output/reddit_ngrams/comment_terms.parquet ${similarity_data}/subreddits_by_num_comments.csv ${tfidf_weekly_data}/comment_authors_30k.parquet
|
||||
${srun_singularity} python3 weekly_cosine_similarities.py authors --topN=30000 --outfile=${similarity_weekly_data}/comment_authors_30k.parquet
|
||||
|
||||
# ${tfidf_weekly_data}/comment_authors_130k.parquet: tfidf.py similarities_helper.py /gscratch/comdata/output/reddit_ngrams/comment_authors.parquet /gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments.csv
|
||||
# start_spark_and_run.sh 1 tfidf.py authors_weekly --topN=130000
|
||||
|
||||
# /gscratch/comdata/output/reddit_similarity/comment_authors_10000.parquet: cosine_similarities.py similarities_helper.py /gscratch/comdata/output/reddit_similarity/tfidf/comment_authors.parquet /gscratch/comdata/output/reddit_similarity/tfidf/comment_authors.parquet
|
||||
# start_spark_and_run.sh 1 cosine_similarities.py author --outfile=/gscratch/comdata/output/reddit_similarity/comment_authors_10000.feather
|
||||
|
||||
# /gscratch/comdata/output/reddit_similarity/comment_terms.parquet: cosine_similarities.py similarities_helper.py /gscratch/comdata/output/reddit_similarity/tfidf/comment_terms.parquet
|
||||
# start_spark_and_run.sh 1 cosine_similarities.py term --outfile=/gscratch/comdata/output/reddit_similarity/comment_terms_10000.feather
|
||||
|
||||
# /gscratch/comdata/output/reddit_similarity/comment_terms_10000_weekly.parquet: cosine_similarities.py ${tfidf_weekly_data}/comment_authors.parquet
|
||||
# start_spark_and_run.sh 1 weekly_cosine_similarities.py term --outfile=/gscratch/comdata/output/reddit_similarity/subreddit_comment_terms_10000_weely.parquet
|
||||
|
||||
# /gscratch/comdata/output/reddit_similarity/subreddit_author_tf_similarities_10000.parquet: cosine_similarities.py similarities_helper.py /gscratch/comdata/output/reddit_similarity/tfidf/comment_authors.parquet /gscratch/comdata/output/reddit_similarity/tfidf/comment_authors.parquet
|
||||
# start_spark_and_run.sh 1 cosine_similarities.py author-tf --outfile=/gscratch/comdata/output/reddit_similarity/subreddit_author_tf_similarities_10000.parquet
|
||||
BIN
similarities/__pycache__/similarities_helper.cpython-37.pyc
Normal file
BIN
similarities/__pycache__/similarities_helper.cpython-37.pyc
Normal file
Binary file not shown.
58
similarities/cosine_similarities.py
Normal file
58
similarities/cosine_similarities.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import pandas as pd
|
||||
import fire
|
||||
from pathlib import Path
|
||||
from similarities_helper import similarities, column_similarities
|
||||
from functools import partial
|
||||
|
||||
def cosine_similarities(infile, term_colname, outfile, min_df=None, max_df=None, included_subreddits=None, topN=500, exclude_phrases=False, from_date=None, to_date=None, tfidf_colname='tf_idf'):
|
||||
|
||||
return similarities(infile=infile, simfunc=column_similarities, term_colname=term_colname, outfile=outfile, min_df=min_df, max_df=max_df, included_subreddits=included_subreddits, topN=topN, exclude_phrases=exclude_phrases,from_date=from_date, to_date=to_date, tfidf_colname=tfidf_colname)
|
||||
|
||||
# change so that these take in an input as an optional argument (for speed, but also for idf).
|
||||
def term_cosine_similarities(outfile, infile='/gscratch/comdata/output/reddit_similarity/tfidf/comment_terms_100k.parquet', min_df=None, max_df=None, included_subreddits=None, topN=500, exclude_phrases=False, from_date=None, to_date=None):
|
||||
|
||||
return cosine_similarities(infile,
|
||||
'term',
|
||||
outfile,
|
||||
min_df,
|
||||
max_df,
|
||||
included_subreddits,
|
||||
topN,
|
||||
exclude_phrases,
|
||||
from_date,
|
||||
to_date
|
||||
)
|
||||
|
||||
def author_cosine_similarities(outfile, infile='/gscratch/comdata/output/reddit_similarity/tfidf/comment_authors_100k.parquet', min_df=2, max_df=None, included_subreddits=None, topN=10000, from_date=None, to_date=None):
|
||||
return cosine_similarities(infile,
|
||||
'author',
|
||||
outfile,
|
||||
min_df,
|
||||
max_df,
|
||||
included_subreddits,
|
||||
topN,
|
||||
exclude_phrases=False,
|
||||
from_date=from_date,
|
||||
to_date=to_date
|
||||
)
|
||||
|
||||
def author_tf_similarities(outfile, infile='/gscratch/comdata/output/reddit_similarity/tfidf/comment_authors_100k.parquet', min_df=2, max_df=None, included_subreddits=None, topN=10000, from_date=None, to_date=None):
|
||||
return cosine_similarities(infile,
|
||||
'author',
|
||||
outfile,
|
||||
min_df,
|
||||
max_df,
|
||||
included_subreddits,
|
||||
topN,
|
||||
exclude_phrases=False,
|
||||
from_date=from_date,
|
||||
to_date=to_date,
|
||||
tfidf_colname='relative_tf'
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire({'term':term_cosine_similarities,
|
||||
'author':author_cosine_similarities,
|
||||
'author-tf':author_tf_similarities})
|
||||
|
||||
4
similarities/job_script.sh
Executable file
4
similarities/job_script.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
start_spark_cluster.sh
|
||||
singularity exec /gscratch/comdata/users/nathante/cdsc_base.sif spark-submit --master spark://$(hostname).hyak.local:7077 lsi_similarities.py author --outfile=/gscratch/comdata/output//reddit_similarity/subreddit_comment_authors_10k_LSI.feather --topN=10000
|
||||
singularity exec /gscratch/comdata/users/nathante/cdsc_base.sif stop-all.sh
|
||||
61
similarities/lsi_similarities.py
Normal file
61
similarities/lsi_similarities.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import pandas as pd
|
||||
import fire
|
||||
from pathlib import Path
|
||||
from similarities_helper import similarities, lsi_column_similarities
|
||||
from functools import partial
|
||||
|
||||
def lsi_similarities(infile, term_colname, outfile, min_df=None, max_df=None, included_subreddits=None, topN=500, from_date=None, to_date=None, tfidf_colname='tf_idf',n_components=100,n_iter=5,random_state=1968,algorithm='arpack'):
|
||||
print(n_components,flush=True)
|
||||
|
||||
simfunc = partial(lsi_column_similarities,n_components=n_components,n_iter=n_iter,random_state=random_state,algorithm=algorithm)
|
||||
|
||||
return similarities(infile=infile, simfunc=simfunc, term_colname=term_colname, outfile=outfile, min_df=min_df, max_df=max_df, included_subreddits=included_subreddits, topN=topN, from_date=from_date, to_date=to_date, tfidf_colname=tfidf_colname)
|
||||
|
||||
# change so that these take in an input as an optional argument (for speed, but also for idf).
|
||||
def term_lsi_similarities(outfile, min_df=None, max_df=None, included_subreddits=None, topN=500, from_date=None, to_date=None, n_components=300,n_iter=5,random_state=1968,algorithm='arpack'):
|
||||
|
||||
return lsi_similarities('/gscratch/comdata/output/reddit_similarity/tfidf/comment_terms_100k.parquet',
|
||||
'term',
|
||||
outfile,
|
||||
min_df,
|
||||
max_df,
|
||||
included_subreddits,
|
||||
topN,
|
||||
from_date,
|
||||
to_date,
|
||||
n_components=n_components
|
||||
)
|
||||
|
||||
def author_lsi_similarities(outfile, min_df=2, max_df=None, included_subreddits=None, topN=10000, from_date=None, to_date=None,n_components=300,n_iter=5,random_state=1968,algorithm='arpack'):
|
||||
return lsi_similarities('/gscratch/comdata/output/reddit_similarity/tfidf/comment_authors_100k.parquet',
|
||||
'author',
|
||||
outfile,
|
||||
min_df,
|
||||
max_df,
|
||||
included_subreddits,
|
||||
topN,
|
||||
from_date=from_date,
|
||||
to_date=to_date,
|
||||
n_components=n_components
|
||||
)
|
||||
|
||||
def author_tf_similarities(outfile, min_df=2, max_df=None, included_subreddits=None, topN=10000, from_date=None, to_date=None,n_components=300,n_iter=5,random_state=1968,algorithm='arpack'):
|
||||
return lsi_similarities('/gscratch/comdata/output/reddit_similarity/tfidf/comment_authors_100k.parquet',
|
||||
'author',
|
||||
outfile,
|
||||
min_df,
|
||||
max_df,
|
||||
included_subreddits,
|
||||
topN,
|
||||
from_date=from_date,
|
||||
to_date=to_date,
|
||||
tfidf_colname='relative_tf',
|
||||
n_components=n_components
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire({'term':term_lsi_similarities,
|
||||
'author':author_lsi_similarities,
|
||||
'author-tf':author_tf_similarities})
|
||||
|
||||
365
similarities/similarities_helper.py
Normal file
365
similarities/similarities_helper.py
Normal file
@@ -0,0 +1,365 @@
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from pyspark.sql import functions as f
|
||||
from enum import Enum
|
||||
from multiprocessing import cpu_count, Pool
|
||||
from pyspark.mllib.linalg.distributed import CoordinateMatrix
|
||||
from tempfile import TemporaryDirectory
|
||||
import pyarrow
|
||||
import pyarrow.dataset as ds
|
||||
from sklearn.metrics import pairwise_distances
|
||||
from scipy.sparse import csr_matrix, issparse
|
||||
from sklearn.decomposition import TruncatedSVD
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import pathlib
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
class tf_weight(Enum):
|
||||
MaxTF = 1
|
||||
Norm05 = 2
|
||||
|
||||
infile = "/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_terms.parquet"
|
||||
cache_file = "/gscratch/comdata/users/nathante/cdsc_reddit/similarities/term_tfidf_entries_bak.parquet"
|
||||
|
||||
def termauthor_tfidf(term_tfidf_callable, author_tfidf_callable):
|
||||
|
||||
|
||||
# subreddits missing after this step don't have any terms that have a high enough idf
|
||||
# try rewriting without merges
|
||||
def reindex_tfidf(infile, term_colname, min_df=None, max_df=None, included_subreddits=None, topN=500, week=None, from_date=None, to_date=None, rescale_idf=True, tf_family=tf_weight.MaxTF):
|
||||
print("loading tfidf", flush=True)
|
||||
tfidf_ds = ds.dataset(infile)
|
||||
|
||||
if included_subreddits is None:
|
||||
included_subreddits = select_topN_subreddits(topN)
|
||||
else:
|
||||
included_subreddits = set(map(str.strip,map(str.lower,open(included_subreddits))))
|
||||
|
||||
ds_filter = ds.field("subreddit").isin(included_subreddits)
|
||||
|
||||
if min_df is not None:
|
||||
ds_filter &= ds.field("count") >= min_df
|
||||
|
||||
if max_df is not None:
|
||||
ds_filter &= ds.field("count") <= max_df
|
||||
|
||||
if week is not None:
|
||||
ds_filter &= ds.field("week") == week
|
||||
|
||||
if from_date is not None:
|
||||
ds_filter &= ds.field("week") >= from_date
|
||||
|
||||
if to_date is not None:
|
||||
ds_filter &= ds.field("week") <= to_date
|
||||
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
term_id_new = term + '_id_new'
|
||||
|
||||
projection = {
|
||||
'subreddit_id':ds.field('subreddit_id'),
|
||||
term_id:ds.field(term_id),
|
||||
'relative_tf':ds.field("relative_tf").cast('float32')
|
||||
}
|
||||
|
||||
if not rescale_idf:
|
||||
projection = {
|
||||
'subreddit_id':ds.field('subreddit_id'),
|
||||
term_id:ds.field(term_id),
|
||||
'relative_tf':ds.field('relative_tf').cast('float32'),
|
||||
'tf_idf':ds.field('tf_idf').cast('float32')}
|
||||
|
||||
tfidf_ds = ds.dataset(infile)
|
||||
|
||||
df = tfidf_ds.to_table(filter=ds_filter,columns=projection)
|
||||
|
||||
df = df.to_pandas(split_blocks=True,self_destruct=True)
|
||||
print("assigning indexes",flush=True)
|
||||
df['subreddit_id_new'] = df.groupby("subreddit_id").ngroup()
|
||||
grouped = df.groupby(term_id)
|
||||
df[term_id_new] = grouped.ngroup()
|
||||
|
||||
if rescale_idf:
|
||||
print("computing idf", flush=True)
|
||||
df['new_count'] = grouped[term_id].transform('count')
|
||||
N_docs = df.subreddit_id_new.max() + 1
|
||||
df['idf'] = np.log(N_docs/(1+df.new_count),dtype='float32') + 1
|
||||
if tf_family == tf_weight.MaxTF:
|
||||
df["tf_idf"] = df.relative_tf * df.idf
|
||||
else: # tf_fam = tf_weight.Norm05
|
||||
df["tf_idf"] = (0.5 + 0.5 * df.relative_tf) * df.idf
|
||||
|
||||
print("assigning names")
|
||||
subreddit_names = tfidf_ds.to_table(filter=ds_filter,columns=['subreddit','subreddit_id'])
|
||||
batches = subreddit_names.to_batches()
|
||||
|
||||
with Pool(cpu_count()) as pool:
|
||||
chunks = pool.imap_unordered(pull_names,batches)
|
||||
subreddit_names = pd.concat(chunks,copy=False).drop_duplicates()
|
||||
|
||||
subreddit_names = subreddit_names.set_index("subreddit_id")
|
||||
new_ids = df.loc[:,['subreddit_id','subreddit_id_new']].drop_duplicates()
|
||||
new_ids = new_ids.set_index('subreddit_id')
|
||||
subreddit_names = subreddit_names.join(new_ids,on='subreddit_id').reset_index()
|
||||
subreddit_names = subreddit_names.drop("subreddit_id",1)
|
||||
subreddit_names = subreddit_names.sort_values("subreddit_id_new")
|
||||
return(df, subreddit_names)
|
||||
|
||||
def pull_names(batch):
|
||||
return(batch.to_pandas().drop_duplicates())
|
||||
|
||||
def similarities(infile, simfunc, term_colname, outfile, min_df=None, max_df=None, included_subreddits=None, topN=500, from_date=None, to_date=None, tfidf_colname='tf_idf'):
|
||||
'''
|
||||
tfidf_colname: set to 'relative_tf' to use normalized term frequency instead of tf-idf, which can be useful for author-based similarities.
|
||||
'''
|
||||
|
||||
def proc_sims(sims, outfile):
|
||||
if issparse(sims):
|
||||
sims = sims.todense()
|
||||
|
||||
print(f"shape of sims:{sims.shape}")
|
||||
print(f"len(subreddit_names.subreddit.values):{len(subreddit_names.subreddit.values)}",flush=True)
|
||||
sims = pd.DataFrame(sims)
|
||||
sims = sims.rename({i:sr for i, sr in enumerate(subreddit_names.subreddit.values)}, axis=1)
|
||||
sims['_subreddit'] = subreddit_names.subreddit.values
|
||||
|
||||
p = Path(outfile)
|
||||
|
||||
output_feather = Path(str(p).replace("".join(p.suffixes), ".feather"))
|
||||
output_csv = Path(str(p).replace("".join(p.suffixes), ".csv"))
|
||||
output_parquet = Path(str(p).replace("".join(p.suffixes), ".parquet"))
|
||||
outfile.parent.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
sims.to_feather(outfile)
|
||||
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
term_id_new = term + '_id_new'
|
||||
|
||||
entries, subreddit_names = reindex_tfidf(infile, term_colname=term_colname, min_df=min_df, max_df=max_df, included_subreddits=included_subreddits, topN=topN,from_date=from_date,to_date=to_date)
|
||||
mat = csr_matrix((entries[tfidf_colname],(entries[term_id_new], entries.subreddit_id_new)))
|
||||
|
||||
print("loading matrix")
|
||||
|
||||
# mat = read_tfidf_matrix("term_tfidf_entries7ejhvnvl.parquet", term_colname)
|
||||
|
||||
print(f'computing similarities on mat. mat.shape:{mat.shape}')
|
||||
print(f"size of mat is:{mat.data.nbytes}",flush=True)
|
||||
sims = simfunc(mat)
|
||||
del mat
|
||||
|
||||
if hasattr(sims,'__next__'):
|
||||
for simmat, name in sims:
|
||||
proc_sims(simmat, Path(outfile)/(str(name) + ".feather"))
|
||||
else:
|
||||
proc_sims(simmat, outfile)
|
||||
|
||||
def write_weekly_similarities(path, sims, week, names):
|
||||
sims['week'] = week
|
||||
p = pathlib.Path(path)
|
||||
if not p.is_dir():
|
||||
p.mkdir(exist_ok=True,parents=True)
|
||||
|
||||
# reformat as a pairwise list
|
||||
sims = sims.melt(id_vars=['_subreddit','week'],value_vars=names.subreddit.values)
|
||||
sims.to_parquet(p / week.isoformat())
|
||||
|
||||
def column_overlaps(mat):
|
||||
non_zeros = (mat != 0).astype('double')
|
||||
|
||||
intersection = non_zeros.T @ non_zeros
|
||||
card1 = non_zeros.sum(axis=0)
|
||||
den = np.add.outer(card1,card1) - intersection
|
||||
|
||||
return intersection / den
|
||||
|
||||
def test_lsi_sims():
|
||||
term = "term"
|
||||
term_id = term + '_id'
|
||||
term_id_new = term + '_id_new'
|
||||
|
||||
t1 = time.perf_counter()
|
||||
entries, subreddit_names = reindex_tfidf("/gscratch/comdata/output/reddit_similarity/tfidf/comment_terms_100k_repartitioned.parquet",
|
||||
term_colname='term',
|
||||
min_df=2000,
|
||||
topN=10000
|
||||
)
|
||||
t2 = time.perf_counter()
|
||||
print(f"first load took:{t2 - t1}s")
|
||||
|
||||
entries, subreddit_names = reindex_tfidf("/gscratch/comdata/output/reddit_similarity/tfidf/comment_terms_100k.parquet",
|
||||
term_colname='term',
|
||||
min_df=2000,
|
||||
topN=10000
|
||||
)
|
||||
t3=time.perf_counter()
|
||||
|
||||
print(f"second load took:{t3 - t2}s")
|
||||
|
||||
mat = csr_matrix((entries['tf_idf'],(entries[term_id_new], entries.subreddit_id_new)))
|
||||
sims = list(lsi_column_similarities(mat, [10,50]))
|
||||
sims_og = sims
|
||||
sims_test = list(lsi_column_similarities(mat,[10,50],algorithm='randomized',n_iter=10))
|
||||
|
||||
# n_components is the latent dimensionality. sklearn recommends 100. More might be better
|
||||
# if n_components is a list we'll return a list of similarities with different latent dimensionalities
|
||||
# if algorithm is 'randomized' instead of 'arpack' then n_iter gives the number of iterations.
|
||||
# this function takes the svd and then the column similarities of it
|
||||
def lsi_column_similarities(tfidfmat,n_components=300,n_iter=10,random_state=1968,algorithm='randomized'):
|
||||
# first compute the lsi of the matrix
|
||||
# then take the column similarities
|
||||
print("running LSI",flush=True)
|
||||
|
||||
if type(n_components) is int:
|
||||
n_components = [n_components]
|
||||
|
||||
n_components = sorted(n_components,reverse=True)
|
||||
|
||||
svd_components = n_components[0]
|
||||
svd = TruncatedSVD(n_components=svd_components,random_state=random_state,algorithm=algorithm,n_iter=n_iter)
|
||||
mod = svd.fit(tfidfmat.T)
|
||||
lsimat = mod.transform(tfidfmat.T)
|
||||
for n_dims in n_components:
|
||||
sims = column_similarities(lsimat[:,np.arange(n_dims)])
|
||||
if len(n_components) > 1:
|
||||
yield (sims, n_dims)
|
||||
else:
|
||||
return sims
|
||||
|
||||
|
||||
def column_similarities(mat):
|
||||
return 1 - pairwise_distances(mat,metric='cosine')
|
||||
|
||||
|
||||
def build_weekly_tfidf_dataset(df, include_subs, term_colname, tf_family=tf_weight.Norm05):
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
|
||||
# aggregate counts by week. now subreddit-term is distinct
|
||||
df = df.filter(df.subreddit.isin(include_subs))
|
||||
df = df.groupBy(['subreddit',term,'week']).agg(f.sum('tf').alias('tf'))
|
||||
|
||||
max_subreddit_terms = df.groupby(['subreddit','week']).max('tf') # subreddits are unique
|
||||
max_subreddit_terms = max_subreddit_terms.withColumnRenamed('max(tf)','sr_max_tf')
|
||||
df = df.join(max_subreddit_terms, on=['subreddit','week'])
|
||||
df = df.withColumn("relative_tf", df.tf / df.sr_max_tf)
|
||||
|
||||
# group by term. term is unique
|
||||
idf = df.groupby([term,'week']).count()
|
||||
|
||||
N_docs = df.select(['subreddit','week']).distinct().groupby(['week']).agg(f.count("subreddit").alias("subreddits_in_week"))
|
||||
|
||||
idf = idf.join(N_docs, on=['week'])
|
||||
|
||||
# add a little smoothing to the idf
|
||||
idf = idf.withColumn('idf',f.log(idf.subreddits_in_week) / (1+f.col('count'))+1)
|
||||
|
||||
# collect the dictionary to make a pydict of terms to indexes
|
||||
terms = idf.select([term,'week']).distinct() # terms are distinct
|
||||
|
||||
terms = terms.withColumn(term_id,f.row_number().over(Window.partitionBy('week').orderBy(term))) # term ids are distinct
|
||||
|
||||
# make subreddit ids
|
||||
subreddits = df.select(['subreddit','week']).distinct()
|
||||
subreddits = subreddits.withColumn('subreddit_id',f.row_number().over(Window.partitionBy("week").orderBy("subreddit")))
|
||||
|
||||
df = df.join(subreddits,on=['subreddit','week'])
|
||||
|
||||
# map terms to indexes in the tfs and the idfs
|
||||
df = df.join(terms,on=[term,'week']) # subreddit-term-id is unique
|
||||
|
||||
idf = idf.join(terms,on=[term,'week'])
|
||||
|
||||
# join on subreddit/term to create tf/dfs indexed by term
|
||||
df = df.join(idf, on=[term_id, term,'week'])
|
||||
|
||||
# agg terms by subreddit to make sparse tf/df vectors
|
||||
|
||||
if tf_family == tf_weight.MaxTF:
|
||||
df = df.withColumn("tf_idf", df.relative_tf * df.idf)
|
||||
else: # tf_fam = tf_weight.Norm05
|
||||
df = df.withColumn("tf_idf", (0.5 + 0.5 * df.relative_tf) * df.idf)
|
||||
|
||||
df = df.repartition(400,'subreddit','week')
|
||||
dfwriter = df.write.partitionBy("week").sortBy("subreddit")
|
||||
return dfwriter
|
||||
|
||||
def _calc_tfidf(df, term_colname, tf_family):
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
|
||||
max_subreddit_terms = df.groupby(['subreddit']).max('tf') # subreddits are unique
|
||||
max_subreddit_terms = max_subreddit_terms.withColumnRenamed('max(tf)','sr_max_tf')
|
||||
|
||||
df = df.join(max_subreddit_terms, on='subreddit')
|
||||
|
||||
df = df.withColumn("relative_tf", (df.tf / df.sr_max_tf))
|
||||
|
||||
# group by term. term is unique
|
||||
idf = df.groupby([term]).count()
|
||||
N_docs = df.select('subreddit').distinct().count()
|
||||
# add a little smoothing to the idf
|
||||
idf = idf.withColumn('idf',f.log(N_docs/(1+f.col('count')))+1)
|
||||
|
||||
# collect the dictionary to make a pydict of terms to indexes
|
||||
terms = idf.select(term).distinct() # terms are distinct
|
||||
terms = terms.withColumn(term_id,f.row_number().over(Window.orderBy(term))) # term ids are distinct
|
||||
|
||||
# make subreddit ids
|
||||
subreddits = df.select(['subreddit']).distinct()
|
||||
subreddits = subreddits.withColumn('subreddit_id',f.row_number().over(Window.orderBy("subreddit")))
|
||||
|
||||
df = df.join(subreddits,on='subreddit')
|
||||
|
||||
# map terms to indexes in the tfs and the idfs
|
||||
df = df.join(terms,on=term) # subreddit-term-id is unique
|
||||
|
||||
idf = idf.join(terms,on=term)
|
||||
|
||||
# join on subreddit/term to create tf/dfs indexed by term
|
||||
df = df.join(idf, on=[term_id, term])
|
||||
|
||||
# agg terms by subreddit to make sparse tf/df vectors
|
||||
if tf_family == tf_weight.MaxTF:
|
||||
df = df.withColumn("tf_idf", df.relative_tf * df.idf)
|
||||
else: # tf_fam = tf_weight.Norm05
|
||||
df = df.withColumn("tf_idf", (0.5 + 0.5 * df.relative_tf) * df.idf)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def build_tfidf_dataset(df, include_subs, term_colname, tf_family=tf_weight.Norm05):
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
# aggregate counts by week. now subreddit-term is distinct
|
||||
df = df.filter(df.subreddit.isin(include_subs))
|
||||
df = df.groupBy(['subreddit',term]).agg(f.sum('tf').alias('tf'))
|
||||
|
||||
df = _calc_tfidf(df, term_colname, tf_family)
|
||||
df = df.repartition('subreddit')
|
||||
dfwriter = df.write.sortBy("subreddit","tf")
|
||||
return dfwriter
|
||||
|
||||
def select_topN_subreddits(topN, path="/gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments_nonsfw.csv"):
|
||||
rankdf = pd.read_csv(path)
|
||||
included_subreddits = set(rankdf.loc[rankdf.comments_rank <= topN,'subreddit'].values)
|
||||
return included_subreddits
|
||||
|
||||
|
||||
def repartition_tfidf(inpath="/gscratch/comdata/output/reddit_similarity/tfidf/comment_terms_100k.parquet",
|
||||
outpath="/gscratch/comdata/output/reddit_similarity/tfidf/comment_terms_100k_repartitioned.parquet"):
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
df = spark.read.parquet(inpath)
|
||||
df = df.repartition(400,'subreddit')
|
||||
df.write.parquet(outpath,mode='overwrite')
|
||||
|
||||
|
||||
def repartition_tfidf_weekly(inpath="/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_terms.parquet",
|
||||
outpath="/gscratch/comdata/output/reddit_similarity/tfidf/comment_terms_repartitioned.parquet"):
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
df = spark.read.parquet(inpath)
|
||||
df = df.repartition(400,'subreddit','week')
|
||||
dfwriter = df.write.partitionBy("week")
|
||||
dfwriter.parquet(outpath,mode='overwrite')
|
||||
83
similarities/tfidf.py
Normal file
83
similarities/tfidf.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import fire
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import functions as f
|
||||
from similarities_helper import build_tfidf_dataset, build_weekly_tfidf_dataset, select_topN_subreddits
|
||||
|
||||
def _tfidf_wrapper(func, inpath, outpath, topN, term_colname, exclude, included_subreddits):
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
|
||||
df = spark.read.parquet(inpath)
|
||||
|
||||
df = df.filter(~ f.col(term_colname).isin(exclude))
|
||||
|
||||
if included_subreddits is not None:
|
||||
include_subs = set(map(str.strip,map(str.lower, open(included_subreddits))))
|
||||
else:
|
||||
include_subs = select_topN_subreddits(topN)
|
||||
|
||||
dfwriter = func(df, include_subs, term_colname)
|
||||
|
||||
dfwriter.parquet(outpath,mode='overwrite',compression='snappy')
|
||||
spark.stop()
|
||||
|
||||
def tfidf(inpath, outpath, topN, term_colname, exclude, included_subreddits):
|
||||
return _tfidf_wrapper(build_tfidf_dataset, inpath, outpath, topN, term_colname, exclude, included_subreddits)
|
||||
|
||||
def tfidf_weekly(inpath, outpath, topN, term_colname, exclude, included_subreddits):
|
||||
return _tfidf_wrapper(build_weekly_tfidf_dataset, inpath, outpath, topN, term_colname, exclude, included_subreddits)
|
||||
|
||||
def tfidf_authors(outpath='/gscratch/comdata/output/reddit_similarity/tfidf/comment_authors.parquet',
|
||||
topN=25000,
|
||||
included_subreddits=None):
|
||||
|
||||
return tfidf("/gscratch/comdata/output/reddit_ngrams/comment_authors.parquet",
|
||||
outpath,
|
||||
topN,
|
||||
'author',
|
||||
['[deleted]','AutoModerator'],
|
||||
included_subreddits=included_subreddits
|
||||
)
|
||||
|
||||
def tfidf_terms(outpath='/gscratch/comdata/output/reddit_similarity/tfidf/comment_terms.parquet',
|
||||
topN=25000,
|
||||
included_subreddits=None):
|
||||
|
||||
return tfidf("/gscratch/comdata/output/reddit_ngrams/comment_terms.parquet",
|
||||
outpath,
|
||||
topN,
|
||||
'term',
|
||||
[],
|
||||
included_subreddits=included_subreddits
|
||||
)
|
||||
|
||||
def tfidf_authors_weekly(outpath='/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_authors.parquet',
|
||||
topN=25000,
|
||||
included_subreddits=None):
|
||||
|
||||
return tfidf_weekly("/gscratch/comdata/output/reddit_ngrams/comment_authors.parquet",
|
||||
outpath,
|
||||
topN,
|
||||
'author',
|
||||
['[deleted]','AutoModerator'],
|
||||
included_subreddits=included_subreddits
|
||||
)
|
||||
|
||||
def tfidf_terms_weekly(outpath='/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_terms.parquet',
|
||||
topN=25000,
|
||||
included_subreddits=None):
|
||||
|
||||
|
||||
return tfidf_weekly("/gscratch/comdata/output/reddit_ngrams/comment_terms.parquet",
|
||||
outpath,
|
||||
topN,
|
||||
'term',
|
||||
[],
|
||||
included_subreddits=included_subreddits
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire({'authors':tfidf_authors,
|
||||
'terms':tfidf_terms,
|
||||
'authors_weekly':tfidf_authors_weekly,
|
||||
'terms_weekly':tfidf_terms_weekly})
|
||||
@@ -1,18 +1,14 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from pyspark.mllib.linalg.distributed import RowMatrix, CoordinateMatrix
|
||||
import numpy as np
|
||||
import pyarrow
|
||||
import pandas as pd
|
||||
import fire
|
||||
from itertools import islice
|
||||
from pathlib import Path
|
||||
from similarities_helper import cosine_similarities
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
conf = spark.sparkContext.getConf()
|
||||
|
||||
submissions = spark.read.parquet("/gscratch/comdata/output/reddit_submissions_by_subreddit.parquet")
|
||||
|
||||
prop_nsfw = submissions.select(['subreddit','over_18']).groupby('subreddit').agg(f.mean(f.col('over_18').astype('double')).alias('prop_nsfw'))
|
||||
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_comments_by_subreddit.parquet")
|
||||
|
||||
# remove /u/ pages
|
||||
@@ -20,6 +16,9 @@ df = df.filter(~df.subreddit.like("u_%"))
|
||||
|
||||
df = df.groupBy('subreddit').agg(f.count('id').alias("n_comments"))
|
||||
|
||||
df = df.join(prop_nsfw,on='subreddit')
|
||||
df = df.filter(df.prop_nsfw < 0.5)
|
||||
|
||||
win = Window.orderBy(f.col('n_comments').desc())
|
||||
df = df.withColumn('comments_rank', f.rank().over(win))
|
||||
|
||||
@@ -27,4 +26,4 @@ df = df.toPandas()
|
||||
|
||||
df = df.sort_values("n_comments")
|
||||
|
||||
df.to_csv('/gscratch/comdata/users/nathante/cdsc-reddit/subreddits_by_num_comments.csv',index=False)
|
||||
df.to_csv('/gscratch/comdata/output/reddit_similarity/subreddits_by_num_comments.csv', index=False)
|
||||
18
similarities/wang_similarity.py
Normal file
18
similarities/wang_similarity.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from similarities_helper import similarities
|
||||
import numpy as np
|
||||
import fire
|
||||
|
||||
def wang_similarity(mat):
|
||||
non_zeros = (mat != 0).astype(np.float32)
|
||||
intersection = non_zeros.T @ non_zeros
|
||||
return intersection
|
||||
|
||||
|
||||
infile="/gscratch/comdata/output/reddit_similarity/tfidf/comment_authors.parquet"; outfile="/gscratch/comdata/output/reddit_similarity/wang_similarity_10000.feather"; min_df=1; included_subreddits=None; topN=10000; exclude_phrases=False; from_date=None; to_date=None
|
||||
|
||||
def wang_overlaps(infile, outfile="/gscratch/comdata/output/reddit_similarity/wang_similarity_10000.feather", min_df=1, max_df=None, included_subreddits=None, topN=10000, exclude_phrases=False, from_date=None, to_date=None):
|
||||
|
||||
return similarities(infile=infile, simfunc=wang_similarity, term_colname='author', outfile=outfile, min_df=min_df, max_df=max_df, included_subreddits=included_subreddits, topN=topN, exclude_phrases=exclude_phrases, from_date=from_date, to_date=to_date)
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire(wang_overlaps)
|
||||
81
similarities/weekly_cosine_similarities.py
Normal file
81
similarities/weekly_cosine_similarities.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
import numpy as np
|
||||
import pyarrow
|
||||
import pyarrow.dataset as ds
|
||||
import pandas as pd
|
||||
import fire
|
||||
from itertools import islice, chain
|
||||
from pathlib import Path
|
||||
from similarities_helper import *
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from functools import partial
|
||||
|
||||
|
||||
def _week_similarities(week, simfunc, tfidf_path, term_colname, min_df, max_df, included_subreddits, topN, outdir:Path):
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
term_id_new = term + '_id_new'
|
||||
print(f"loading matrix: {week}")
|
||||
entries, subreddit_names = reindex_tfidf(infile = tfidf_path,
|
||||
term_colname=term_colname,
|
||||
min_df=min_df,
|
||||
max_df=max_df,
|
||||
included_subreddits=included_subreddits,
|
||||
topN=topN,
|
||||
week=week)
|
||||
mat = csr_matrix((entries[tfidf_colname],(entries[term_id_new], entries.subreddit_id_new)))
|
||||
print('computing similarities')
|
||||
sims = column_similarities(mat)
|
||||
del mat
|
||||
sims = pd.DataFrame(sims.todense())
|
||||
sims = sims.rename({i: sr for i, sr in enumerate(subreddit_names.subreddit.values)}, axis=1)
|
||||
sims['_subreddit'] = names.subreddit.values
|
||||
outfile = str(Path(outdir) / str(week))
|
||||
write_weekly_similarities(outfile, sims, week, names)
|
||||
|
||||
def pull_weeks(batch):
|
||||
return set(batch.to_pandas()['week'])
|
||||
|
||||
#tfidf = spark.read.parquet('/gscratch/comdata/users/nathante/subreddit_tfidf_weekly.parquet')
|
||||
def cosine_similarities_weekly(tfidf_path, outfile, term_colname, min_df = None, max_df=None, included_subreddits = None, topN = 500):
|
||||
print(outfile)
|
||||
tfidf_ds = ds.dataset(tfidf_path)
|
||||
tfidf_ds = tfidf_ds.to_table(columns=["week"])
|
||||
batches = tfidf_ds.to_batches()
|
||||
|
||||
with Pool(cpu_count()) as pool:
|
||||
weeks = set(chain( * pool.imap_unordered(pull_weeks,batches)))
|
||||
|
||||
weeks = sorted(weeks)
|
||||
# do this step in parallel if we have the memory for it.
|
||||
# should be doable with pool.map
|
||||
|
||||
print(f"computing weekly similarities")
|
||||
week_similarities_helper = partial(_week_similarities,simfunc=column_similarities, tfidf_path=tfidf_path, term_colname=term_colname, outdir=outfile, min_df=min_df,max_df=max_df,included_subreddits=included_subreddits,topN=topN)
|
||||
|
||||
with Pool(cpu_count()) as pool: # maybe it can be done with 40 cores on the huge machine?
|
||||
list(pool.map(week_similarities_helper,weeks))
|
||||
|
||||
def author_cosine_similarities_weekly(outfile, min_df=2, max_df=None, included_subreddits=None, topN=500):
|
||||
return cosine_similarities_weekly('/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_authors.parquet',
|
||||
outfile,
|
||||
'author',
|
||||
min_df,
|
||||
max_df,
|
||||
included_subreddits,
|
||||
topN)
|
||||
|
||||
def term_cosine_similarities_weekly(outfile, min_df=None, max_df=None, included_subreddits=None, topN=500):
|
||||
return cosine_similarities_weekly('/gscratch/comdata/output/reddit_similarity/tfidf_weekly/comment_terms.parquet',
|
||||
outfile,
|
||||
'term',
|
||||
min_df,
|
||||
max_df,
|
||||
included_subreddits,
|
||||
topN)
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire({'authors':author_cosine_similarities_weekly,
|
||||
'terms':term_cosine_similarities_weekly})
|
||||
@@ -1,172 +0,0 @@
|
||||
from pyspark.sql import Window
|
||||
from pyspark.sql import functions as f
|
||||
from enum import Enum
|
||||
from pyspark.mllib.linalg.distributed import CoordinateMatrix
|
||||
from tempfile import TemporaryDirectory
|
||||
import pyarrow
|
||||
import pyarrow.dataset as ds
|
||||
from scipy.sparse import csr_matrix
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
class tf_weight(Enum):
|
||||
MaxTF = 1
|
||||
Norm05 = 2
|
||||
|
||||
def read_tfidf_matrix(path,term_colname):
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
term_id_new = term + '_id_new'
|
||||
|
||||
dataset = ds.dataset(path,format='parquet')
|
||||
entries = dataset.to_table(columns=['tf_idf','subreddit_id_new',term_id_new]).to_pandas()
|
||||
return(csr_matrix((entries.tf_idf,(entries[term_id_new]-1, entries.subreddit_id_new-1))))
|
||||
|
||||
def column_similarities(mat):
|
||||
norm = np.matrix(np.power(mat.power(2).sum(axis=0),0.5,dtype=np.float32))
|
||||
mat = mat.multiply(1/norm)
|
||||
sims = mat.T @ mat
|
||||
return(sims)
|
||||
|
||||
|
||||
def prep_tfidf_entries(tfidf, term_colname, min_df, included_subreddits):
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
term_id_new = term + '_id_new'
|
||||
|
||||
if min_df is None:
|
||||
min_df = 0.1 * len(included_subreddits)
|
||||
|
||||
tfidf = tfidf.filter(f.col("subreddit").isin(included_subreddits))
|
||||
|
||||
# reset the subreddit ids
|
||||
sub_ids = tfidf.select('subreddit_id').distinct()
|
||||
sub_ids = sub_ids.withColumn("subreddit_id_new",f.row_number().over(Window.orderBy("subreddit_id")))
|
||||
tfidf = tfidf.join(sub_ids,'subreddit_id')
|
||||
|
||||
# only use terms in at least min_df included subreddits
|
||||
new_count = tfidf.groupBy(term_id).agg(f.count(term_id).alias('new_count'))
|
||||
# new_count = new_count.filter(f.col('new_count') >= min_df)
|
||||
tfidf = tfidf.join(new_count,term_id,how='inner')
|
||||
|
||||
# reset the term ids
|
||||
term_ids = tfidf.select([term_id]).distinct()
|
||||
term_ids = term_ids.withColumn(term_id_new,f.row_number().over(Window.orderBy(term_id)))
|
||||
tfidf = tfidf.join(term_ids,term_id)
|
||||
|
||||
tfidf = tfidf.withColumnRenamed("tf_idf","tf_idf_old")
|
||||
# tfidf = tfidf.withColumnRenamed("idf","idf_old")
|
||||
# tfidf = tfidf.withColumn("idf",f.log(25000/f.col("count")))
|
||||
tfidf = tfidf.withColumn("tf_idf", (tfidf.relative_tf * tfidf.idf).cast('float'))
|
||||
|
||||
tempdir =TemporaryDirectory(suffix='.parquet',prefix='term_tfidf_entries',dir='.')
|
||||
|
||||
tfidf.write.parquet(tempdir.name,mode='overwrite',compression='snappy')
|
||||
return tempdir
|
||||
|
||||
def cosine_similarities(tfidf, term_colname, min_df, included_subreddits, similarity_threshold):
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
term_id_new = term + '_id_new'
|
||||
|
||||
if min_df is None:
|
||||
min_df = 0.1 * len(included_subreddits)
|
||||
|
||||
tfidf = tfidf.filter(f.col("subreddit").isin(included_subreddits))
|
||||
tfidf = tfidf.cache()
|
||||
|
||||
# reset the subreddit ids
|
||||
sub_ids = tfidf.select('subreddit_id').distinct()
|
||||
sub_ids = sub_ids.withColumn("subreddit_id_new",f.row_number().over(Window.orderBy("subreddit_id")))
|
||||
tfidf = tfidf.join(sub_ids,'subreddit_id')
|
||||
|
||||
# only use terms in at least min_df included subreddits
|
||||
new_count = tfidf.groupBy(term_id).agg(f.count(term_id).alias('new_count'))
|
||||
# new_count = new_count.filter(f.col('new_count') >= min_df)
|
||||
tfidf = tfidf.join(new_count,term_id,how='inner')
|
||||
|
||||
# reset the term ids
|
||||
term_ids = tfidf.select([term_id]).distinct()
|
||||
term_ids = term_ids.withColumn(term_id_new,f.row_number().over(Window.orderBy(term_id)))
|
||||
tfidf = tfidf.join(term_ids,term_id)
|
||||
|
||||
tfidf = tfidf.withColumnRenamed("tf_idf","tf_idf_old")
|
||||
# tfidf = tfidf.withColumnRenamed("idf","idf_old")
|
||||
# tfidf = tfidf.withColumn("idf",f.log(25000/f.col("count")))
|
||||
tfidf = tfidf.withColumn("tf_idf", tfidf.relative_tf * tfidf.idf)
|
||||
|
||||
# step 1 make an rdd of entires
|
||||
# sorted by (dense) spark subreddit id
|
||||
# entries = tfidf.filter((f.col('subreddit') == 'asoiaf') | (f.col('subreddit') == 'gameofthrones') | (f.col('subreddit') == 'christianity')).select(f.col("term_id_new")-1,f.col("subreddit_id_new")-1,"tf_idf").rdd
|
||||
|
||||
n_partitions = int(len(included_subreddits)*2 / 5)
|
||||
|
||||
entries = tfidf.select(f.col(term_id_new)-1,f.col("subreddit_id_new")-1,"tf_idf").rdd.repartition(n_partitions)
|
||||
|
||||
# put like 10 subredis in each partition
|
||||
|
||||
# step 2 make it into a distributed.RowMatrix
|
||||
coordMat = CoordinateMatrix(entries)
|
||||
|
||||
coordMat = CoordinateMatrix(coordMat.entries.repartition(n_partitions))
|
||||
|
||||
# this needs to be an IndexedRowMatrix()
|
||||
mat = coordMat.toRowMatrix()
|
||||
|
||||
#goal: build a matrix of subreddit columns and tf-idfs rows
|
||||
sim_dist = mat.columnSimilarities(threshold=similarity_threshold)
|
||||
|
||||
return (sim_dist, tfidf)
|
||||
|
||||
|
||||
def build_tfidf_dataset(df, include_subs, term_colname, tf_family=tf_weight.Norm05):
|
||||
|
||||
term = term_colname
|
||||
term_id = term + '_id'
|
||||
# aggregate counts by week. now subreddit-term is distinct
|
||||
df = df.filter(df.subreddit.isin(include_subs))
|
||||
df = df.groupBy(['subreddit',term]).agg(f.sum('tf').alias('tf'))
|
||||
|
||||
max_subreddit_terms = df.groupby(['subreddit']).max('tf') # subreddits are unique
|
||||
max_subreddit_terms = max_subreddit_terms.withColumnRenamed('max(tf)','sr_max_tf')
|
||||
|
||||
df = df.join(max_subreddit_terms, on='subreddit')
|
||||
|
||||
df = df.withColumn("relative_tf", df.tf / df.sr_max_tf)
|
||||
|
||||
# group by term. term is unique
|
||||
idf = df.groupby([term]).count()
|
||||
|
||||
N_docs = df.select('subreddit').distinct().count()
|
||||
|
||||
# add a little smoothing to the idf
|
||||
idf = idf.withColumn('idf',f.log(N_docs/(1+f.col('count')))+1)
|
||||
|
||||
# collect the dictionary to make a pydict of terms to indexes
|
||||
terms = idf.select(term).distinct() # terms are distinct
|
||||
terms = terms.withColumn(term_id,f.row_number().over(Window.orderBy(term))) # term ids are distinct
|
||||
|
||||
# make subreddit ids
|
||||
subreddits = df.select(['subreddit']).distinct()
|
||||
subreddits = subreddits.withColumn('subreddit_id',f.row_number().over(Window.orderBy("subreddit")))
|
||||
|
||||
df = df.join(subreddits,on='subreddit')
|
||||
|
||||
# map terms to indexes in the tfs and the idfs
|
||||
df = df.join(terms,on=term) # subreddit-term-id is unique
|
||||
|
||||
idf = idf.join(terms,on=term)
|
||||
|
||||
# join on subreddit/term to create tf/dfs indexed by term
|
||||
df = df.join(idf, on=[term_id, term])
|
||||
|
||||
# agg terms by subreddit to make sparse tf/df vectors
|
||||
|
||||
if tf_family == tf_weight.MaxTF:
|
||||
df = df.withColumn("tf_idf", df.relative_tf * df.idf)
|
||||
else: # tf_fam = tf_weight.Norm05
|
||||
df = df.withColumn("tf_idf", (0.5 + 0.5 * df.relative_tf) * df.idf)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from pyspark.mllib.linalg.distributed import RowMatrix, CoordinateMatrix
|
||||
import numpy as np
|
||||
import pyarrow
|
||||
import pandas as pd
|
||||
import fire
|
||||
from itertools import islice
|
||||
from pathlib import Path
|
||||
from similarities_helper import cosine_similarities, prep_tfidf_entries, read_tfidf_matrix, column_similarities
|
||||
import scipy
|
||||
# outfile='test_similarities_500.feather';
|
||||
# min_df = None;
|
||||
# included_subreddits=None; topN=100; exclude_phrases=True;
|
||||
|
||||
def term_cosine_similarities(outfile, min_df = None, included_subreddits=None, topN=500, exclude_phrases=False):
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
conf = spark.sparkContext.getConf()
|
||||
print(outfile)
|
||||
print(exclude_phrases)
|
||||
|
||||
tfidf = spark.read.parquet('/gscratch/comdata/users/nathante/subreddit_tfidf.parquet')
|
||||
|
||||
if included_subreddits is None:
|
||||
rankdf = pd.read_csv("/gscratch/comdata/users/nathante/cdsc-reddit/subreddits_by_num_comments.csv")
|
||||
included_subreddits = set(rankdf.loc[rankdf.comments_rank <= topN,'subreddit'].values)
|
||||
|
||||
else:
|
||||
included_subreddits = set(open(included_subreddits))
|
||||
|
||||
if exclude_phrases == True:
|
||||
tfidf = tfidf.filter(~f.col(term).contains("_"))
|
||||
|
||||
print("creating temporary parquet with matrix indicies")
|
||||
tempdir = prep_tfidf_entries(tfidf, 'term', min_df, included_subreddits)
|
||||
tfidf = spark.read.parquet(tempdir.name)
|
||||
subreddit_names = tfidf.select(['subreddit','subreddit_id_new']).distinct().toPandas()
|
||||
subreddit_names = subreddit_names.sort_values("subreddit_id_new")
|
||||
subreddit_names['subreddit_id_new'] = subreddit_names['subreddit_id_new'] - 1
|
||||
spark.stop()
|
||||
|
||||
print("loading matrix")
|
||||
mat = read_tfidf_matrix(tempdir.name,'term')
|
||||
print('computing similarities')
|
||||
sims = column_similarities(mat)
|
||||
del mat
|
||||
|
||||
sims = pd.DataFrame(sims.todense())
|
||||
sims = sims.rename({i:sr for i, sr in enumerate(subreddit_names.subreddit.values)},axis=1)
|
||||
sims['subreddit'] = subreddit_names.subreddit.values
|
||||
|
||||
p = Path(outfile)
|
||||
|
||||
output_feather = Path(str(p).replace("".join(p.suffixes), ".feather"))
|
||||
output_csv = Path(str(p).replace("".join(p.suffixes), ".csv"))
|
||||
output_parquet = Path(str(p).replace("".join(p.suffixes), ".parquet"))
|
||||
|
||||
sims.to_feather(outfile)
|
||||
tempdir.cleanup()
|
||||
path = "term_tfidf_entriesaukjy5gv.parquet"
|
||||
|
||||
|
||||
# outfile = '/gscratch/comdata/users/nathante/test_similarities_500.feather'; min_df = None; included_subreddits=None; similarity_threshold=0;
|
||||
# def term_cosine_similarities(outfile, min_df = None, included_subreddits=None, similarity_threshold=0, topN=500, exclude_phrases=True):
|
||||
# '''
|
||||
# Compute similarities between subreddits based on tfi-idf vectors of comment texts
|
||||
|
||||
# included_subreddits : string
|
||||
# Text file containing a list of subreddits to include (one per line) if included_subreddits is None then do the top 500 subreddits
|
||||
|
||||
# similarity_threshold : double (default = 0)
|
||||
# set > 0 for large numbers of subreddits to get an approximate solution using the DIMSUM algorithm
|
||||
# https://stanford.edu/~rezab/papers/dimsum.pdf. If similarity_threshold=0 we get an exact solution using an O(N^2) algorithm.
|
||||
|
||||
# min_df : int (default = 0.1 * (number of included_subreddits)
|
||||
# exclude terms that appear in fewer than this number of documents.
|
||||
|
||||
# outfile: string
|
||||
# where to output csv and feather outputs
|
||||
# '''
|
||||
|
||||
# print(outfile)
|
||||
# print(exclude_phrases)
|
||||
|
||||
# tfidf = spark.read.parquet('/gscratch/comdata/users/nathante/subreddit_tfidf.parquet')
|
||||
|
||||
# if included_subreddits is None:
|
||||
# included_subreddits = list(islice(open("/gscratch/comdata/users/nathante/cdsc-reddit/top_25000_subs_by_comments.txt"),topN))
|
||||
# included_subreddits = {s.strip('\n') for s in included_subreddits}
|
||||
|
||||
# else:
|
||||
# included_subreddits = set(open(included_subreddits))
|
||||
|
||||
# if exclude_phrases == True:
|
||||
# tfidf = tfidf.filter(~f.col(term).contains("_"))
|
||||
|
||||
# sim_dist, tfidf = cosine_similarities(tfidf, 'term', min_df, included_subreddits, similarity_threshold)
|
||||
|
||||
# p = Path(outfile)
|
||||
|
||||
# output_feather = Path(str(p).replace("".join(p.suffixes), ".feather"))
|
||||
# output_csv = Path(str(p).replace("".join(p.suffixes), ".csv"))
|
||||
# output_parquet = Path(str(p).replace("".join(p.suffixes), ".parquet"))
|
||||
|
||||
# sim_dist.entries.toDF().write.parquet(str(output_parquet),mode='overwrite',compression='snappy')
|
||||
|
||||
# #instead of toLocalMatrix() why not read as entries and put strait into numpy
|
||||
# sim_entries = pd.read_parquet(output_parquet)
|
||||
|
||||
# df = tfidf.select('subreddit','subreddit_id_new').distinct().toPandas()
|
||||
# spark.stop()
|
||||
# df['subreddit_id_new'] = df['subreddit_id_new'] - 1
|
||||
# df = df.sort_values('subreddit_id_new').reset_index(drop=True)
|
||||
# df = df.set_index('subreddit_id_new')
|
||||
|
||||
# similarities = sim_entries.join(df, on='i')
|
||||
# similarities = similarities.rename(columns={'subreddit':"subreddit_i"})
|
||||
# similarities = similarities.join(df, on='j')
|
||||
# similarities = similarities.rename(columns={'subreddit':"subreddit_j"})
|
||||
|
||||
# similarities.to_feather(output_feather)
|
||||
# similarities.to_csv(output_csv)
|
||||
# return similarities
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(term_cosine_similarities)
|
||||
@@ -1,19 +0,0 @@
|
||||
from pyspark.sql import SparkSession
|
||||
from similarities_helper import build_tfidf_dataset
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
|
||||
df = spark.read.parquet("/gscratch/comdata/users/nathante/reddit_tfidf_test_authors.parquet_temp")
|
||||
|
||||
include_subs = set(open("/gscratch/comdata/users/nathante/cdsc-reddit/top_25000_subs_by_comments.txt"))
|
||||
include_subs = {s.strip('\n') for s in include_subs}
|
||||
|
||||
# remove [deleted] and AutoModerator (TODO remove other bots)
|
||||
df = df.filter(df.author != '[deleted]')
|
||||
df = df.filter(df.author != 'AutoModerator')
|
||||
|
||||
df = build_tfidf_dataset(df, include_subs, 'author')
|
||||
|
||||
df.write.parquet('/gscratch/comdata/users/nathante/subreddit_tfidf_authors.parquet',mode='overwrite',compression='snappy')
|
||||
|
||||
spark.stop()
|
||||
@@ -1,18 +0,0 @@
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from pyspark.sql import Window
|
||||
from similarities_helper import build_tfidf_dataset
|
||||
|
||||
## TODO:need to exclude automoderator / bot posts.
|
||||
## TODO:need to exclude better handle hyperlinks.
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
df = spark.read.parquet("/gscratch/comdata/users/nathante/reddit_tfidf_test.parquet_temp")
|
||||
|
||||
include_subs = set(open("/gscratch/comdata/users/nathante/cdsc-reddit/top_25000_subs_by_comments.txt"))
|
||||
include_subs = {s.strip('\n') for s in include_subs}
|
||||
|
||||
df = build_tfidf_dataset(df, include_subs, 'term')
|
||||
|
||||
df.write.parquet('/gscratch/comdata/users/nathante/subreddit_tfidf.parquet',mode='overwrite',compression='snappy')
|
||||
spark.stop()
|
||||
96
timeseries/choose_clusters.py
Normal file
96
timeseries/choose_clusters.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from pyarrow import dataset as ds
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import plotnine as pn
|
||||
random = np.random.RandomState(1968)
|
||||
|
||||
def load_densities(term_density_file="/gscratch/comdata/output/reddit_density/comment_terms_10000.feather",
|
||||
author_density_file="/gscratch/comdata/output/reddit_density/comment_authors_10000.feather"):
|
||||
|
||||
term_density = pd.read_feather(term_density_file)
|
||||
author_density = pd.read_feather(author_density_file)
|
||||
|
||||
term_density.rename({'overlap_density':'term_density','index':'subreddit'},axis='columns',inplace=True)
|
||||
author_density.rename({'overlap_density':'author_density','index':'subreddit'},axis='columns',inplace=True)
|
||||
|
||||
density = term_density.merge(author_density,on='subreddit',how='inner')
|
||||
|
||||
return density
|
||||
|
||||
def load_clusters(term_clusters_file="/gscratch/comdata/output/reddit_clustering/comment_terms_10000.feather",
|
||||
author_clusters_file="/gscratch/comdata/output/reddit_clustering/comment_authors_10000.feather"):
|
||||
term_clusters = pd.read_feather(term_clusters_file)
|
||||
author_clusters = pd.read_feather(author_clusters_file)
|
||||
|
||||
# rename, join and return
|
||||
term_clusters.rename({'cluster':'term_cluster'},axis='columns',inplace=True)
|
||||
author_clusters.rename({'cluster':'author_cluster'},axis='columns',inplace=True)
|
||||
|
||||
clusters = term_clusters.merge(author_clusters,on='subreddit',how='inner')
|
||||
|
||||
return clusters
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
df = load_densities()
|
||||
cl = load_clusters()
|
||||
|
||||
df['td_rank'] = df.term_density.rank()
|
||||
df['ad_rank'] = df.author_density.rank()
|
||||
|
||||
df['td_percentile'] = df.td_rank / df.shape[0]
|
||||
df['ad_percentile'] = df.ad_rank / df.shape[0]
|
||||
|
||||
df = df.merge(cl, on='subreddit',how='inner')
|
||||
|
||||
term_cluster_density = df.groupby('term_cluster').agg({'td_rank':['mean','min','max'],
|
||||
'ad_rank':['mean','min','max'],
|
||||
'td_percentile':['mean','min','max'],
|
||||
'ad_percentile':['mean','min','max'],
|
||||
'subreddit':['count']})
|
||||
|
||||
|
||||
author_cluster_density = df.groupby('author_cluster').agg({'td_rank':['mean','min','max'],
|
||||
'ad_rank':['mean','min','max'],
|
||||
'td_percentile':['mean','min','max'],
|
||||
'ad_percentile':['mean','min','max'],
|
||||
'subreddit':['count']})
|
||||
|
||||
# which clusters have the most term_density?
|
||||
term_cluster_density.iloc[term_cluster_density.td_rank['mean'].sort_values().index]
|
||||
|
||||
# which clusters have the most author_density?
|
||||
term_cluster_density.iloc[term_cluster_density.ad_rank['mean'].sort_values(ascending=False).index].loc[term_cluster_density.subreddit['count'] >= 5][0:20]
|
||||
|
||||
high_density_term_clusters = term_cluster_density.loc[(term_cluster_density.td_percentile['mean'] > 0.75) & (term_cluster_density.subreddit['count'] > 5)]
|
||||
|
||||
# let's just use term density instead of author density for now. We can do a second batch with author density next.
|
||||
chosen_clusters = high_density_term_clusters.sample(3,random_state=random)
|
||||
|
||||
cluster_info = df.loc[df.term_cluster.isin(chosen_clusters.index.values)]
|
||||
|
||||
chosen_subreddits = cluster_info.subreddit.values
|
||||
|
||||
dataset = ds.dataset("/gscratch/comdata/output/reddit_comments_by_subreddit.parquet",format='parquet')
|
||||
comments = dataset.to_table(filter=ds.field("subreddit").isin(chosen_subreddits),columns=['id','subreddit','author','CreatedAt'])
|
||||
|
||||
comments = comments.to_pandas()
|
||||
|
||||
comments['week'] = comments.CreatedAt.dt.date - pd.to_timedelta(comments['CreatedAt'].dt.dayofweek, unit='d')
|
||||
|
||||
author_timeseries = comments.loc[:,['subreddit','author','week']].drop_duplicates().groupby(['subreddit','week']).count().reset_index()
|
||||
|
||||
for clid in chosen_clusters.index.values:
|
||||
|
||||
ts = pd.read_feather(f"data/ts_term_cluster_{clid}.feather")
|
||||
|
||||
pn.options.figure_size = (11.7,8.27)
|
||||
p = pn.ggplot(ts)
|
||||
p = p + pn.geom_line(pn.aes('week','value',group='subreddit'))
|
||||
p = p + pn.facet_wrap('~ subreddit')
|
||||
p.save(f"plots/ts_term_cluster_{clid}.png")
|
||||
|
||||
|
||||
fig, ax = pyplot.subplots(figsize=(11.7,8.27))
|
||||
g = sns.FacetGrid(ts,row='subreddit')
|
||||
g.map_dataframe(sns.scatterplot,'week','value',data=ts,ax=ax)
|
||||
37
timeseries/cluster_timeseries.py
Normal file
37
timeseries/cluster_timeseries.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from pyspark.sql import functions as f
|
||||
from pyspark.sql import SparkSession
|
||||
from choose_clusters import load_clusters, load_densities
|
||||
import fire
|
||||
from pathlib import Path
|
||||
|
||||
def main(term_clusters_path="/gscratch/comdata/output/reddit_clustering/comment_terms_10000.feather",
|
||||
author_clusters_path="/gscratch/comdata/output/reddit_clustering/comment_authors_10000.feather",
|
||||
term_densities_path="/gscratch/comdata/output/reddit_density/comment_terms_10000.feather",
|
||||
author_densities_path="/gscratch/comdata/output/reddit_density/comment_authors_10000.feather",
|
||||
output="data/subreddit_timeseries.parquet"):
|
||||
|
||||
|
||||
clusters = load_clusters(term_clusters_path, author_clusters_path)
|
||||
densities = load_densities(term_densities_path, author_densities_path)
|
||||
|
||||
spark = SparkSession.builder.getOrCreate()
|
||||
|
||||
df = spark.read.parquet("/gscratch/comdata/output/reddit_comments_by_subreddit.parquet")
|
||||
|
||||
df = df.withColumn('week', f.date_trunc('week', f.col("CreatedAt")))
|
||||
|
||||
# time of unique authors by series by week
|
||||
ts = df.select(['subreddit','week','author']).distinct().groupby(['subreddit','week']).count()
|
||||
|
||||
ts = ts.repartition('subreddit')
|
||||
spk_clusters = spark.createDataFrame(clusters)
|
||||
|
||||
ts = ts.join(spk_clusters, on='subreddit', how='inner')
|
||||
spk_densities = spark.createDataFrame(densities)
|
||||
ts = ts.join(spk_densities, on='subreddit', how='inner')
|
||||
ts.write.parquet(output, mode='overwrite')
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire(main)
|
||||
11
visualization/Makefile
Normal file
11
visualization/Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
all: subreddit_author_tf_similarities_10000.html #comment_authors_10000.html
|
||||
|
||||
# wang_tsne_10000.html
|
||||
# wang_tsne_10000.html:/gscratch/comdata/output/reddit_tsne/wang_similarity_10000.feather /gscratch/comdata/output/reddit_clustering/wang_similarity_10000.feather tsne_vis.py
|
||||
# python3 tsne_vis.py --tsne_data=/gscratch/comdata/output/reddit_tsne/wang_similarity_10000.feather --clusters=/gscratch/comdata/output/reddit_clustering/wang_similarity_10000.feather --output=wang_tsne_10000.html
|
||||
|
||||
# comment_authors_10000.html:/gscratch/comdata/output/reddit_tsne/comment_authors_10000.feather /gscratch/comdata/output/reddit_clustering/comment_authors_10000.feather tsne_vis.py
|
||||
# python3 tsne_vis.py --tsne_data=/gscratch/comdata/output/reddit_similarity/comment_authors_10000.feather --clusters=/gscratch/comdata/output/reddit_clustering/comment_authors_10000.feather --output=comment_authors_10000.html
|
||||
|
||||
subreddit_author_tf_similarities_10000.html:/gscratch/comdata/output/reddit_tsne/subreddit_author_tf_similarities_10000.feather /gscratch/comdata/output/reddit_clustering/subreddit_author_tf_similarities_10000.feather tsne_vis.py
|
||||
start_spark_and_run.sh 1 tsne_vis.py --tsne_data=/gscratch/comdata/output/reddit_tsne/subreddit_author_tf_similarities_10000.feather --clusters=/gscratch/comdata/output/reddit_clustering/subreddit_author_tf_similarities_10000.feather --output=subreddit_author_tf_similarities_10000.html
|
||||
35
visualization/subreddit_author_tf_similarities_10000.html
Normal file
35
visualization/subreddit_author_tf_similarities_10000.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,21 +5,44 @@ alt.data_transformers.enable('default')
|
||||
from sklearn.neighbors import NearestNeighbors
|
||||
import pandas as pd
|
||||
from numpy import random
|
||||
import fire
|
||||
import numpy as np
|
||||
|
||||
def base_plot(plot_data):
|
||||
|
||||
# base = base.encode(alt.Color(field='color',type='nominal',scale=alt.Scale(scheme='category10')))
|
||||
|
||||
cluster_dropdown = alt.binding_select(options=[str(c) for c in sorted(set(plot_data.cluster))])
|
||||
|
||||
# subreddit_dropdown = alt.binding_select(options=sorted(plot_data.subreddit))
|
||||
|
||||
cluster_click_select = alt.selection_single(on='click',fields=['cluster'], bind=cluster_dropdown, name=' ')
|
||||
# cluster_select = alt.selection_single(fields=['cluster'], bind=cluster_dropdown, name='cluster')
|
||||
# cluster_select_and = cluster_click_select & cluster_select
|
||||
#
|
||||
# subreddit_select = alt.selection_single(on='click',fields=['subreddit'],bind=subreddit_dropdown,name='subreddit_click')
|
||||
|
||||
color = alt.condition(cluster_click_select ,
|
||||
alt.Color(field='color',type='nominal',scale=alt.Scale(scheme='category10')),
|
||||
alt.value("lightgray"))
|
||||
|
||||
|
||||
base = alt.Chart(plot_data).mark_text().encode(
|
||||
alt.X('x',axis=alt.Axis(grid=False),scale=alt.Scale(domain=(-65,65))),
|
||||
alt.Y('y',axis=alt.Axis(grid=False),scale=alt.Scale(domain=(-65,65))),
|
||||
color=color,
|
||||
text='subreddit')
|
||||
|
||||
base = base.add_selection(cluster_click_select)
|
||||
|
||||
|
||||
return base
|
||||
|
||||
def zoom_plot(plot_data):
|
||||
chart = base_plot(plot_data)
|
||||
chart = chart.encode(alt.Color(field='color',type='nominal',scale=alt.Scale(scheme='category10')))
|
||||
|
||||
chart = chart.interactive()
|
||||
chart = chart.properties(width=1275,height=1000)
|
||||
chart = chart.properties(width=1275,height=800)
|
||||
|
||||
return chart
|
||||
|
||||
@@ -51,7 +74,7 @@ def viewport_plot(plot_data):
|
||||
alt.Y('y',axis=alt.Axis(grid=False),scale=alt.Scale(domain=selectory2))
|
||||
)
|
||||
|
||||
sr = sr.encode(alt.Color(field='color',type='nominal',scale=alt.Scale(scheme='category10')))
|
||||
|
||||
sr = sr.properties(width=1275,height=600)
|
||||
|
||||
|
||||
@@ -70,15 +93,29 @@ def assign_cluster_colors(tsne_data, clusters, n_colors, n_neighbors = 4):
|
||||
distances = np.empty(shape=(centroids.shape[0],centroids.shape[0]))
|
||||
|
||||
groups = tsne_data.groupby('cluster')
|
||||
for centroid in centroids.itertuples():
|
||||
c_dists = groups.apply(lambda r: min(np.sqrt(np.square(centroid.x - r.x) + np.square(centroid.y-r.y))))
|
||||
distances[:,centroid.Index] = c_dists
|
||||
|
||||
points = np.array(tsne_data.loc[:,['x','y']])
|
||||
centers = np.array(centroids.loc[:,['x','y']])
|
||||
|
||||
# point x centroid
|
||||
point_center_distances = np.linalg.norm((points[:,None,:] - centers[None,:,:]),axis=-1)
|
||||
|
||||
# distances is cluster x point
|
||||
for gid, group in groups:
|
||||
c_dists = point_center_distances[group.index.values,:].min(axis=0)
|
||||
distances[group.cluster.values[0],] = c_dists
|
||||
|
||||
# nbrs = NearestNeighbors(n_neighbors=n_neighbors).fit(centroids)
|
||||
# distances, indices = nbrs.kneighbors()
|
||||
|
||||
nbrs = NearestNeighbors(n_neighbors=n_neighbors,metric='precomputed').fit(distances)
|
||||
distances, indices = nbrs.kneighbors()
|
||||
nearest = distances.argpartition(n_neighbors,0)
|
||||
indices = nearest[:n_neighbors,:].T
|
||||
# neighbor_distances = np.copy(distances)
|
||||
# neighbor_distances.sort(0)
|
||||
# neighbor_distances = neighbor_distances[0:n_neighbors,:]
|
||||
|
||||
# nbrs = NearestNeighbors(n_neighbors=n_neighbors,metric='precomputed').fit(distances)
|
||||
# distances, indices = nbrs.kneighbors()
|
||||
|
||||
color_assignments = np.repeat(-1,len(centroids))
|
||||
|
||||
@@ -100,26 +137,39 @@ def assign_cluster_colors(tsne_data, clusters, n_colors, n_neighbors = 4):
|
||||
tsne_data = tsne_data.merge(colors,on='cluster')
|
||||
return(tsne_data)
|
||||
|
||||
term_data = pd.read_feather("tsne_subreddit_fit.feather")
|
||||
clusters = pd.read_feather("term_3000_clusters.feather")
|
||||
def build_visualization(tsne_data, clusters, output):
|
||||
|
||||
tsne_data = assign_cluster_colors(term_data,clusters,10,8)
|
||||
# tsne_data = "/gscratch/comdata/output/reddit_tsne/subreddit_author_tf_similarities_10000.feather"
|
||||
# clusters = "/gscratch/comdata/output/reddit_clustering/subreddit_author_tf_similarities_10000.feather"
|
||||
|
||||
tsne_data = pd.read_feather(tsne_data)
|
||||
clusters = pd.read_feather(clusters)
|
||||
|
||||
tsne_data = assign_cluster_colors(tsne_data,clusters,10,8)
|
||||
|
||||
# sr_per_cluster = tsne_data.groupby('cluster').subreddit.count().reset_index()
|
||||
# sr_per_cluster = sr_per_cluster.rename(columns={'subreddit':'cluster_size'})
|
||||
|
||||
tsne_data = tsne_data.merge(sr_per_cluster,on='cluster')
|
||||
|
||||
term_zoom_plot = zoom_plot(tsne_data)
|
||||
|
||||
term_zoom_plot.save("subreddit_terms_tsne_3000.html")
|
||||
term_zoom_plot.save(output)
|
||||
|
||||
term_viewport_plot = viewport_plot(tsne_data)
|
||||
|
||||
term_viewport_plot.save("subreddit_terms_tsne_3000_viewport.html")
|
||||
term_viewport_plot.save(output.replace(".html","_viewport.html"))
|
||||
|
||||
commenter_data = pd.read_feather("tsne_author_fit.feather")
|
||||
clusters = pd.read_feather('author_3000_clusters.feather')
|
||||
commenter_data = assign_cluster_colors(commenter_data,clusters,10,8)
|
||||
commenter_zoom_plot = zoom_plot(commenter_data)
|
||||
commenter_viewport_plot = viewport_plot(commenter_data)
|
||||
commenter_zoom_plot.save("subreddit_commenters_tsne_3000.html")
|
||||
commenter_viewport_plot.save("subreddit_commenters_tsne_3000_viewport.html")
|
||||
if __name__ == "__main__":
|
||||
fire.Fire(build_visualization)
|
||||
|
||||
# commenter_data = pd.read_feather("tsne_author_fit.feather")
|
||||
# clusters = pd.read_feather('author_3000_clusters.feather')
|
||||
# commenter_data = assign_cluster_colors(commenter_data,clusters,10,8)
|
||||
# commenter_zoom_plot = zoom_plot(commenter_data)
|
||||
# commenter_viewport_plot = viewport_plot(commenter_data)
|
||||
# commenter_zoom_plot.save("subreddit_commenters_tsne_3000.html")
|
||||
# commenter_viewport_plot.save("subreddit_commenters_tsne_3000_viewport.html")
|
||||
|
||||
# chart = chart.properties(width=10000,height=10000)
|
||||
# chart.save("test_tsne_whole.svg")
|
||||
|
||||
Reference in New Issue
Block a user