Who writes like whom, from the prose alone — no genre labels, no metadata.
Every novel in English shares most of its vocabulary with every other novel in English. Said, man, little, know. That common layer is the bulk of the text and it tells you nothing, precisely because everyone uses it.
So throw it away. What is left after you subtract the words everybody uses is the vocabulary that actually distinguishes one writer from another — and two books that still overlap after that subtraction have something real in common.
def remove_all_overlaps(graph_edges, novel_most_used):
all_overlap = None
for edge in graph_edges:
set_1 = set(novel_most_used[edge[0]].keys())
set_2 = set(novel_most_used[edge[1]].keys())
curr_overlap = set_1 & set_2
if all_overlap is None:
all_overlap = curr_overlap
else:
all_overlap = all_overlap & curr_overlap
for novel in novel_most_used:
for i in list(all_overlap):
novel_most_used[novel].pop(i)Read the two loops as one sentence: the first builds the set of words present in every book by intersecting pair after pair; the second deletes that set from all of them. A word has to be missing from at least one novel to survive. That is a much harsher filter than a stop-word list, and it is corpus-relative — run it on four Fitzgerald novels and it strips Fitzgerald's own habits, which is what makes within-author comparison work at all.
Because the edges carry weight, you can ask for the route between two writers who share almost nothing directly — and get back the chain of intermediate books that connects them. The graph doesn't just say who is near whom; it says how you would get from one to the other.
The same file carries the fix for Novel Sentiment Analysis:
sentiment_by_parts(novel, scale, jump) scores overlapping windows rather than
paragraphs — scale sets the window, jump the stride. Same book, same
library, readable curve. And sentiment_multiple_novels normalises several books to the
same number of parts so they can share an axis.
Two of the choices here are the ones Literature Mutations went back and replaced. The hard intersection became TF-IDF with a document-frequency cut (drop vocabulary common to more than 40% of books), which is the same instinct with a dial on it; and the all-pairs edge set became k-nearest-neighbours, because over long English prose a global similarity threshold gives you one undifferentiated blob.