Skip to content

Instantly share code, notes, and snippets.

@me-suzy
Created July 27, 2026 17:25
Show Gist options
  • Select an option

  • Save me-suzy/8c0d3e62a162f0ed77cb2019d5c5aba4 to your computer and use it in GitHub Desktop.

Select an option

Save me-suzy/8c0d3e62a162f0ed77cb2019d5c5aba4 to your computer and use it in GitHub Desktop.
Analiza stil autori
"""
Analiza stilistica (stilometrie): captureaza "amprenta de scris" a fiecarui autor
si ii diferentiaza. NU foloseste embeddings semantice (acelea prind subiectul);
foloseste trasaturi de STIL, care raman constante indiferent despre ce scrie autorul.
Trei familii de trasaturi, combinate:
1. N-grame de caractere -> ortografie, morfologie, forme de epoca
2. Cuvinte-unelte -> semnatura stilistica clasica (cum leaga frazele)
3. Trasaturi structurale -> lungimea frazelor, punctuatie, diversitate lexicala
Instalare:
pip install scikit-learn numpy
"""
import re
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import StandardScaler
# ----------------------------------------------------------------------
# DATE: pentru fiecare autor, fragmentele lui de text (din cartile scanate).
# Aici doua stiluri contrastante pe teme suprapuse, ca demonstratie.
# ----------------------------------------------------------------------
autori = {
"Autor_1880": [
"Bolnavul, cuprins fiind de o mare fierbințeală și de o slăbiciune a mădularelor, se cade a fi așezat la odihnă într-o încăpere răcoroasă și ferită de lumină.",
"Se cuvine, după cum învățătura cea veche ne arată, a-i da bolnavului fierturi ușoare de ierburi, spre a-i alina suferința și a-i întări inima.",
"Rana, de va fi cuprinsă de putrejune și de materie urât mirositoare, se va spăla cu luare-aminte și se va oblojii cu prișnițe calde.",
"Iar de va stărui boala și noaptea nu va putea bolnavul a se odihni, i se va da fiertură de tei cu miere, spre a-i aduce somnul cel dulce.",
],
"Autor_1920": [
"Pacientul cu febră mare și astenie se așează în repaus, în cameră răcoroasă, fără lumină puternică.",
"Se administrează ceaiuri ușoare de plante. Scop: calmarea și susținerea cordului.",
"Rană infectată, cu puroi: se spală atent. Se aplică comprese calde.",
"În caz de insomnie persistentă, se recomandă infuzie de tei cu miere, seara.",
],
}
# ----------------------------------------------------------------------
# 1. TRASATURI STRUCTURALE (calculate manual, per fragment)
# ----------------------------------------------------------------------
def trasaturi_structurale(text):
fraze = [f for f in re.split(r"[.!?]", text) if f.strip()]
cuvinte = re.findall(r"\w+", text.lower())
n_cuv = max(len(cuvinte), 1)
lung_fraze = [len(re.findall(r"\w+", f)) for f in fraze] or [0]
return [
np.mean(lung_fraze), # lungimea medie a frazei
np.std(lung_fraze), # cat de variabile sunt frazele
len(set(cuvinte)) / n_cuv, # diversitate lexicala (type-token ratio)
text.count(",") / n_cuv, # densitatea virgulelor
(text.count(";") + text.count(":")) / n_cuv, # punctuatie complexa
np.mean([len(c) for c in cuvinte]), # lungimea medie a cuvantului
]
# ----------------------------------------------------------------------
# PREGATIRE
# ----------------------------------------------------------------------
texte, etichete = [], []
for autor, fragmente in autori.items():
for f in fragmente:
texte.append(f)
etichete.append(autor)
autori_unici = sorted(set(etichete))
# ----------------------------------------------------------------------
# 2. N-GRAME DE CARACTERE (ortografie + morfologie + forme arhaice)
# ----------------------------------------------------------------------
vec_char = TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 4), min_df=1)
X_char = vec_char.fit_transform(texte).toarray()
# ----------------------------------------------------------------------
# 3. CUVINTE-UNELTE (semnatura stilistica: cum leaga autorul ideile)
# Cuvinte fara continut tematic - de aceea prind stilul, nu subiectul.
# ----------------------------------------------------------------------
cuvinte_unelte = [
"si", "și", "a", "de", "la", "cu", "in", "în", "se", "va", "va",
"care", "ce", "spre", "iar", "dar", "insa", "însă", "fiind", "cade",
"cuvine", "dupa", "după", "cel", "cea", "va", "i", "il", "îl", "ne",
]
vec_func = TfidfVectorizer(vocabulary=list(set(cuvinte_unelte)), analyzer="word")
X_func = vec_func.fit_transform(texte).toarray()
# ----------------------------------------------------------------------
# 4. STRUCTURA
# ----------------------------------------------------------------------
X_struct = StandardScaler().fit_transform(
np.array([trasaturi_structurale(t) for t in texte])
)
# ----------------------------------------------------------------------
# COMBINARE (cu ponderi - n-gramele si structura conteaza cel mai mult)
# ----------------------------------------------------------------------
def normalizeaza(M):
n = np.linalg.norm(M, axis=1, keepdims=True)
n[n == 0] = 1
return M / n
X = np.hstack([
normalizeaza(X_char) * 1.0,
normalizeaza(X_func) * 0.8,
normalizeaza(X_struct) * 1.0,
])
# ----------------------------------------------------------------------
# 5. AMPRENTA fiecarui autor = media fragmentelor lui
# ----------------------------------------------------------------------
amprente = {}
for a in autori_unici:
idx = [i for i, e in enumerate(etichete) if e == a]
amprente[a] = X[idx].mean(axis=0)
# ----------------------------------------------------------------------
# 6. CAT DE DISTINCTI sunt autorii intre ei
# ----------------------------------------------------------------------
print("=== SEPARAREA STILISTICA INTRE AUTORI ===")
A = np.vstack([amprente[a] for a in autori_unici])
S = cosine_similarity(A)
for i, a in enumerate(autori_unici):
for j, b in enumerate(autori_unici):
if i < j:
dist = 1 - S[i, j]
verdict = "stiluri distincte" if dist > 0.3 else "stiluri apropiate"
print(f" {a} vs {b}: distanta {dist:.3f} ({verdict})")
print()
# ----------------------------------------------------------------------
# 7. PROFIL STILISTIC: ce deosebeste fiecare autor (masuri interpretabile)
# ----------------------------------------------------------------------
print("=== PROFILUL FIECARUI AUTOR ===")
for a in autori_unici:
idx = [i for i, e in enumerate(etichete) if e == a]
feats = np.array([trasaturi_structurale(texte[i]) for i in idx])
m = feats.mean(axis=0)
print(f" {a}:")
print(f" fraza medie: {m[0]:.1f} cuvinte")
print(f" variatie fraze: {m[1]:.1f}")
print(f" diversitate lexicala: {m[2]:.2f}")
print(f" virgule/cuvant: {m[3]:.3f}")
print()
# ----------------------------------------------------------------------
# 8. ATRIBUIRE: un fragment necunoscut - al cui stil e?
# ----------------------------------------------------------------------
def atribuie(text):
xc = normalizeaza(vec_char.transform([text]).toarray())
xf = normalizeaza(vec_func.transform([text]).toarray()) * 0.8
xs = normalizeaza(StandardScaler().fit(
np.array([trasaturi_structurale(t) for t in texte])
).transform([trasaturi_structurale(text)]))
x = np.hstack([xc, xf, xs])[0]
print(f"Fragment necunoscut: „{text[:55]}...”")
scoruri = [(a, cosine_similarity([x], [amprente[a]])[0, 0]) for a in autori_unici]
for a, s in sorted(scoruri, key=lambda t: -t[1]):
print(f" {a}: {s:.3f}")
castigator = max(scoruri, key=lambda t: t[1])[0]
print(f" -> atribuit lui {castigator}\n")
print("=== ATRIBUIREA UNUI TEXT NECUNOSCUT ===")
atribuie("Iar bolnavul, fiind cuprins de friguri și de mare slăbiciune, se cade a se odihni în tihnă.")
atribuie("Febră, frisoane, oboseală. Repaus la pat. Lichide.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment