Отчёт по исследовательской работе по программированию (12.02.2020)

Выполнили Ковригин Александр и Панюхин Никита

Импортируем библиотеки и небольшая настройка

In [0]:
import os
import re
from typing import List, Tuple
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from dataclasses import dataclass, field

from IPython.display import display
from IPython.display import set_matplotlib_formats
from tqdm import tqdm_notebook as tqdm

%matplotlib inline
set_matplotlib_formats('svg')

Константы

In [0]:
NORM = 100000
DATA_DIRECTORY = "./data/"
FONT_SIZE = 6
DOT_SIZE = 10
COLOR_SCHEME = [
    "#0000ff",
    "#ff0000",
    "#00ff00"
]
PHRASES_FILE = "_phrases.txt"

Функция поиска фразы в тексте

In [0]:
def find(phrase: str, text: str) -> int:
    pattern = r"(\s|^)" + phrase + r"([\;\,\.\s\!\?\:\-]|$)"
    return len(re.findall(pattern, text))

Считываем файл с фразами

In [0]:
with open(os.path.join(DATA_DIRECTORY, PHRASES_FILE), "r", encoding='utf-8') as file:
    phrases = list(filter(lambda x: x, list(map(lambda x: x.strip().lower(), file.readlines()))))
phrases_count = len(phrases)

Класс результатов анализа с методами визуализации

In [0]:
@dataclass
class WordAnalysisResult:
    total_values: np.array
    target_values: np.array
    filename: str = ''
    delta: np.array = field(init=False)
    
    def __post_init__(self):
        self.delta = [abs(self.target_values[i] - self.total_values[i]) for i in range(phrases_count)]
    
    def view_as_table(self):
        nums = list(range(phrases_count))
        indexes = [y for x, y in sorted(zip(self.delta, nums), reverse=True)]
        
        data = np.array(np.array([(phrases[i],
                                   self.total_values[i],
                                   self.target_values[i],
                                   self.delta[i]) for i in indexes]))
        df = pd.DataFrame(data=data,
                          columns=['Word',
                                   'Average frequency of word occurrence (occurrences/' + str(NORM) + ' words)',
                                   'Target average frequency of word occurrence (occurrences/' + str(NORM) + ' words)',
                                   'Delta (occurrences/' + str(NORM) + ' words)'])
        df.name = 'Word occurrences difference in ' + self.filename
        display(df)
        return self
    
    def view_as_plot(self):
        def f(o):
            return [y for x, y in sorted(zip(self.delta, o))]
        sorted_total_values = f(self.total_values)
        sorted_target_values = f(self.target_values)
        sorted_phrases = f(phrases)
        
        sorted_delta = sorted(self.delta)
            
        plt.rc('font', size=FONT_SIZE)          # controls default text sizes
        plt.rc('axes', titlesize=FONT_SIZE)     # fontsize of the axes title
        plt.rc('axes', labelsize=8)     # fontsize of the x and y labels
        plt.rc('xtick', labelsize=FONT_SIZE)    # fontsize of the tick labels
        plt.rc('ytick', labelsize=FONT_SIZE)    # fontsize of the tick labels
        plt.rc('legend', fontsize=FONT_SIZE)    # legend fontsize
        plt.rc('figure', titlesize=FONT_SIZE)   # fontsize of the figure title
        
        plt.xticks(rotation=-90)
        
        plt.scatter(sorted_phrases, sorted_total_values, c=COLOR_SCHEME[0], s=DOT_SIZE).set_label('All works')
        plt.scatter(sorted_phrases, sorted_target_values, c=COLOR_SCHEME[1], s=DOT_SIZE).set_label('Target work')
        plt.scatter(sorted_phrases, sorted_delta, c=COLOR_SCHEME[2], s=DOT_SIZE).set_label('Delta')
        plt.legend(loc="center left")
        plt.show()
        return self

Обработка текстов

In [0]:
def analyze(target_file: str, exclude: List[str] = None) -> WordAnalysisResult:
    if exclude is None:
	    exclude = []
    total_text_length = 0
    target_text_length = 0
    total_values = np.full(phrases_count, 0, float)
    target_values = np.full(phrases_count, 0, float)
    
    pbar = tqdm(os.listdir(DATA_DIRECTORY))
    for filename in pbar:
        if filename == "_phrases.txt":
            continue
        pbar.set_description("Processing %s" % filename)
    
        with open(os.path.join(DATA_DIRECTORY, filename), "r", encoding='utf-8') as file:
            text = file.read().strip().lower()
    
        if filename in exclude:
            continue
        elif filename == target_file:
            target_text_length += len(text.split())
            for i in range(phrases_count):
                if text.count(phrases[i]) != 0:
                    target_values[i] += find(phrases[i], text)
        else:
            total_text_length += len(text.split())
            for i in range(phrases_count):
                if text.count(phrases[i]) != 0:
                    total_values[i] += find(phrases[i], text)

    for i in range(phrases_count):
        total_values[i] *= len(phrases[i]) * NORM / total_text_length
        target_values[i] *= len(phrases[i]) * NORM / target_text_length
    
    return WordAnalysisResult(total_values, target_values, target_file)
In [7]:
r1 = analyze("don.txt", exclude=["dostoevsky.txt"]).view_as_table().view_as_plot()

Word Average frequency of word occurrence (occurrences/100000 words) Target average frequency of word occurrence (occurrences/100000 words) Delta (occurrences/100000 words)
0 должно 91.90394335994009 143.63338256615944 51.729439206219354
1 может быть 127.64436577769456 79.04352814175441 48.60083763594015
2 наверное 104.3847257915369 61.42811329873485 42.956612492802044
3 очевидно 65.80776191205587 25.29392900536141 40.51383290669446
4 что ли 129.3462906547305 94.85223377010529 34.49405688462521
5 по всей вероятности 0.0 30.036540693866677 30.036540693866677
6 вроде 89.3510560443862 64.36401577257145 24.98704027181475
7 знать 63.82218288884728 39.521764070877204 24.300418817970076
8 как видно 71.48084483550896 48.781148796054154 22.699696039454807
9 что ль 6.807699508143711 27.10063822003008 20.29293871188637
10 должно быть 46.80293411848801 67.07407959457446 20.271145476086446
11 возможно 22.69233169381237 5.420127644006016 17.272204049806355
12 небось 142.96168967101792 159.8937654981775 16.932075827159565
13 видимо 28.93272290961077 44.716053063049635 15.783330153438865
14 чай 0.0 14.227835065515793 14.227835065515793
15 скорее всего 13.615399016287421 0.0 13.615399016287421
16 видно 201.39444378258474 214.5467192419048 13.152275459320066
17 думается 0.0 12.646964502680705 12.646964502680705
18 наверно 19.855790232085823 7.90435281417544 11.951437417910384
19 видать 64.67314532736525 52.84624452905866 11.826900798306589
20 вроде бы 11.346165846906185 0.0 11.346165846906185
21 вероятно 18.153865355049895 27.10063822003008 8.946772864980186
22 судя по всему 0.0 8.807707421509777 8.807707421509777
23 пожалуй 47.65389655700597 39.521764070877204 8.132132486128768
24 поди 14.75001560097804 21.680510576024066 6.930494975046026
25 надо полагать 0.0 5.871804947673184 5.871804947673184
26 верно 63.82218288884728 58.71804947673185 5.104133412115431
27 по-видимому 0.0 4.9684503403388485 4.9684503403388485
28 по всему вероятию 4.8221204849351285 0.0 4.8221204849351285
29 как я погляжу 3.68750390024451 0.0 3.68750390024451
30 вернее всего 3.4038497540718553 0.0 3.4038497540718553
31 кажется 39.71158046417165 36.36002294520703 3.3515575189646185
32 надо быть 7.658661946661675 6.097643599506769 1.5610183471549055
33 может статься 3.68750390024451 2.935902473836592 0.7516014264079178
34 надо думать 3.1201956078992006 2.4842251701694242 0.6359704377297764
35 чаятельно 0.0 0.0 0.0
36 по всем видимостям 0.0 0.0 0.0
37 по всем вероятностям 0.0 0.0 0.0
38 по всем вероятиям 0.0 0.0 0.0
39 по всей видимости 0.0 0.0 0.0
40 по видимости 0.0 0.0 0.0
41 очень может быть 0.0 0.0 0.0
42 не исключено, что 0.0 0.0 0.0
43 может случиться 0.0 0.0 0.0
44 как можно заключить 0.0 0.0 0.0
45 как мне кажется 0.0 0.0 0.0
46 как мне видится 0.0 0.0 0.0
47 как видится 0.0 0.0 0.0
48 к тому идет, что 0.0 0.0 0.0
49 к тому дело идет, что 0.0 0.0 0.0
50 есть шансы, что 0.0 0.0 0.0
51 должно статься 0.0 0.0 0.0
52 должно полагать 0.0 0.0 0.0
53 верней всего 0.0 0.0 0.0

Посмотрим на график. Заметим, что чуть с большей/меньшей частотой встречаются слова и фразы "может быть", "должно", "наверное" и т.д.

Для уверенности также проверим на отличность произведение "Поднятая Целина":

In [8]:
r2 = analyze("tselina.txt", exclude=["don.txt", "dostoevsky.txt"]).view_as_table().view_as_plot()

Word Average frequency of word occurrence (occurrences/100000 words) Target average frequency of word occurrence (occurrences/100000 words) Delta (occurrences/100000 words)
0 верно 35.94148725874277 81.99524896100306 46.05376170226029
1 как видно 45.28627394601589 88.55486887788331 43.26859493186742
2 видать 38.816806239442194 81.5267046812259 42.7098984417837
3 должно быть 23.721381590770225 61.84784493058517 38.12646333981495
4 может быть 107.8244617762283 140.5632839331481 32.73882215691981
5 вероятно 0.0 29.986833905738262 29.986833905738262
6 наверное 86.25956942098264 116.19898138473576 29.939411963753116
7 поди 31.628508787693633 3.7483542382172828 27.88015454947635
8 скорее всего 0.0 22.490125429303696 22.490125429303696
9 небось 129.38935413147397 151.80834664779994 22.41899251632597
10 знать 50.318082162239875 72.62436336545986 22.30628120321998
11 что ли 142.32828954462136 120.88442418250736 21.443865362113996
12 надо быть 0.0 12.65069555398333 12.65069555398333
13 пожалуй 40.254465729791896 52.47695933504196 12.222493605250065
14 видно 208.46062610070805 196.78859750640734 11.672028594300713
15 вроде 82.66542069510837 93.70885595543207 11.043435260323704
16 что ль 12.938935413147398 2.811265678662962 10.127669734484435
17 вроде бы 17.251913884196526 7.4967084764345655 9.75520540776196
18 должно 86.25956942098264 95.58303307454071 9.323463653558065
19 возможно 17.251913884196526 26.23847966752098 8.986565783324455
20 по всему вероятию 0.0 7.965252756211726 7.965252756211726
21 надо думать 7.907127196923408 0.0 7.907127196923408
22 может статься 0.0 6.091075637103084 6.091075637103084
23 как я погляжу 0.0 6.091075637103084 6.091075637103084
24 вернее всего 0.0 5.622531357325924 5.622531357325924
25 очевидно 63.257017575387266 67.4703762879111 4.213358712523828
26 видимо 30.190849297343927 28.11265678662962 2.078192510714306
27 кажется 40.254465729791896 39.35771950128147 0.8967462285104233
28 наверно 20.127232864895948 19.678859750640736 0.44837311425521165
29 чаятельно 0.0 0.0 0.0
30 чай 0.0 0.0 0.0
31 судя по всему 0.0 0.0 0.0
32 по-видимому 0.0 0.0 0.0
33 по всем видимостям 0.0 0.0 0.0
34 по всем вероятностям 0.0 0.0 0.0
35 по всем вероятиям 0.0 0.0 0.0
36 по всей видимости 0.0 0.0 0.0
37 по всей вероятности 0.0 0.0 0.0
38 по видимости 0.0 0.0 0.0
39 очень может быть 0.0 0.0 0.0
40 не исключено, что 0.0 0.0 0.0
41 надо полагать 0.0 0.0 0.0
42 может случиться 0.0 0.0 0.0
43 как можно заключить 0.0 0.0 0.0
44 как мне кажется 0.0 0.0 0.0
45 как мне видится 0.0 0.0 0.0
46 как видится 0.0 0.0 0.0
47 к тому идет, что 0.0 0.0 0.0
48 к тому дело идет, что 0.0 0.0 0.0
49 есть шансы, что 0.0 0.0 0.0
50 думается 0.0 0.0 0.0
51 должно статься 0.0 0.0 0.0
52 должно полагать 0.0 0.0 0.0
53 верней всего 0.0 0.0 0.0

Видно, что распределение $\Delta$ для обоих произведений довольно близко. Хотя для "Тихого Дона" $\Delta_{max}$ всё-таки чуть больше.

Теперь для сравнения посмотрим на совсем другого автора, например, Достоевского. Проанализируем "Преступление и Наказание".

In [9]:
r3 = analyze("dostoevsky.txt", exclude=["don.txt"]).view_as_table().view_as_plot()

Word Average frequency of word occurrence (occurrences/100000 words) Target average frequency of word occurrence (occurrences/100000 words) Delta (occurrences/100000 words)
0 может быть 127.64436577769456 987.0488796995215 859.404513921827
1 кажется 39.71158046417165 552.7473726317321 513.0357921675604
2 пожалуй 47.65389655700597 301.82915742390634 254.17526086690037
3 должно быть 46.80293411848801 194.2927794776953 147.48984535920727
4 наверно 19.855790232085823 156.36932252081894 136.51353228873313
5 вероятно 18.153865355049895 153.7718254689781 135.6179601139282
6 по-видимому 0.0 125.71885730909695 125.71885730909695
7 небось 142.96168967101792 21.818975235463107 121.14271443555481
8 видно 201.39444378258474 103.89988207363385 97.4945617089509
9 наверное 104.3847257915369 24.935971697672123 79.44875409386478
10 возможно 22.69233169381237 95.58789150774315 72.89555981393079
11 что ль 6.807699508143711 74.80791509301636 68.00021558487265
12 должно 91.90394335994009 152.73282664824174 60.828883288301654
13 видать 64.67314532736525 6.233992924418031 58.439152402947215
14 как видно 71.48084483550896 18.701978773254094 52.77886606225486
15 видимо 28.93272290961077 77.92491155522538 48.992188645614604
16 вроде 89.3510560443862 49.35244398497608 39.99861205941012
17 очевидно 65.80776191205587 103.89988207363385 38.09212016157798
18 чай 0.0 35.84545931540368 35.84545931540368
19 очень может быть 0.0 33.24796226356283 33.24796226356283
20 знать 63.82218288884728 96.1073909181113 32.28520802926403
21 надо быть 7.658661946661675 32.728462853194664 25.06980090653299
22 может случиться 0.0 23.377473466567615 23.377473466567615
23 что ли 129.3462906547305 149.61583018603272 20.26953953130223
24 вернее всего 3.4038497540718553 18.70197877325409 15.298129019182236
25 скорее всего 13.615399016287421 0.0 13.615399016287421
26 вроде бы 11.346165846906185 0.0 11.346165846906185
27 верно 63.82218288884728 72.72991745154368 8.907734562696405
28 надо полагать 0.0 6.7534923347862 6.7534923347862
29 по всему вероятию 4.8221204849351285 0.0 4.8221204849351285
30 может статься 3.68750390024451 0.0 3.68750390024451
31 как я погляжу 3.68750390024451 0.0 3.68750390024451
32 надо думать 3.1201956078992006 0.0 3.1201956078992006
33 поди 14.75001560097804 16.623981131781417 1.8739655308033765
34 чаятельно 0.0 0.0 0.0
35 судя по всему 0.0 0.0 0.0
36 по всем видимостям 0.0 0.0 0.0
37 по всем вероятностям 0.0 0.0 0.0
38 по всем вероятиям 0.0 0.0 0.0
39 по всей видимости 0.0 0.0 0.0
40 по всей вероятности 0.0 0.0 0.0
41 по видимости 0.0 0.0 0.0
42 не исключено, что 0.0 0.0 0.0
43 как можно заключить 0.0 0.0 0.0
44 как мне кажется 0.0 0.0 0.0
45 как мне видится 0.0 0.0 0.0
46 как видится 0.0 0.0 0.0
47 к тому идет, что 0.0 0.0 0.0
48 к тому дело идет, что 0.0 0.0 0.0
49 есть шансы, что 0.0 0.0 0.0
50 думается 0.0 0.0 0.0
51 должно статься 0.0 0.0 0.0
52 должно полагать 0.0 0.0 0.0
53 верней всего 0.0 0.0 0.0

Если раньше $\Delta$ не превышало и 60, то теперь она бывает и под 800. Конечно, трудно попорить, что Фёдор Михайлович - совсем другой автор, но это всё равно даёт хорошее представление о работе нашего метода.

Из всего этого можно сделать вывод, что скорее всего Михаил Шолохов действительно является автором "Тихого Дона".