import logging
import os
import re
import time
from collections import Counter, defaultdict
from operator import itemgetter

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from fuzzywuzzy import process, fuzz
from matplotlib_venn import venn2

import pybel
from pybel.constants import PYBEL_DATA_DIR
from pybel.manager.cache import CacheManager
from pybel.parser import MetadataParser

import pybel_tools as pbt
from pybel_tools.utils import barh, barv

%config InlineBackend.figure_format = 'svg'
%matplotlib inline

logging.getLogger('pybel.cache').setLevel(logging.CRITICAL)

"""
Explanation: Abstract
Author: Charles Tapley Hoyt
This notebook outlines the systematic assessment of errors in a BEL document. The data used is from the Alzheimer's Disease (AD) knowledge assembly model that has been annotated with the NeuromMMSig Database. Error analysis is not meant to place blame on contributors to a BEL document, but rather to inform curation leaders where recuration efforts should be focused and to make analysts aware of the issues with a given BEL document.
Notebook Setup
End of explanation
"""


time.asctime()

pybel.__version__

pbt.__version__

"""
Explanation: Notebook Provenance
The time of execution and the versions of the software packegs used are displayed explicitly.
End of explanation
"""


bms_base = os.environ['BMS_BASE']

"""
Explanation: Local Path Definitions
To make this notebook interoperable across many machines, locations to the repositories that contain the data used in this notebook are referenced from the environment, set in ~/.bashrc to point to the place where the repositories have been cloned. Assuming the repositories have been git clone'd into the ~/dev folder, the entries in ~/.bashrc should look like:
bash
...
export BMS_BASE=~/dev/bms
export BANANA_BASE=~/dev/banana
export PYBEL_RESOURCES_BASE=~/dev/pybel-resources
...
BMS
The biological model store (BMS) is the internal Fraunhofer SCAI repository for keeping BEL models under version control. It can be downloaded from https://tor-2.scai.fraunhofer.de/gf/project/bms/
End of explanation
"""


pybel_resources_base = os.environ['PYBEL_RESOURCES_BASE']

"""
Explanation: PyBEL Resources
PyBEL resources is a set of namespaces and annotations that have been made avaliable by the AETIONOMY project through collaboration between the PyBEL Core team and the NeuromMMSig Database developers. It can be downloaded from https://github.com/pybel/pybel-resources
End of explanation
"""


pickle_path = os.path.join(bms_base, 'aetionomy', 'alzheimers', 'alzheimers.gpickle')

graph = pybel.from_pickle(pickle_path)

graph.version

"""
Explanation: Data
The Alzheimer's Disease Knowledge Assembly has been precompiled with the following command line script, and will be loaded from this format for improved performance. In general, derived data, such as the gpickle representation of a BEL script, are not saved under version control to ensure that the most up-to-date data is always used.
sh
pybel convert --path "$BMS_BASE/aetionomy/alzheimers.bel" --pickle "$BMS_BASE/aetionomy/alzheimers.gpickle"
The BEL script can also be compiled from inside this notebook with the following python code:
```python



import os
import pybel
Input from BEL script
bel_path = os.path.join(bms_base, 'aetionomy', 'alzheimers.bel')
graph = pybel.from_path(bel_path)
Output to gpickle for fast loading later
pickle_path = os.path.join(bms_base, 'aetionomy', 'alzheimers.gpickle')
pybel.to_pickle(graph, pickle_path)
```
End of explanation
"""


len(graph.warnings)

"""
Explanation: Error Analysis
As stated in the pybel.BELGraph documentation, all warnings during BEL compilation are stored in a list. These contain information about the BEL statement, the line number, the type of error, and the current annotations in the parser at the time of error. PyBEL tools makes many functions avaliable for systematically analyzing these errors.
The total number of errors is listed below.
End of explanation
"""


error_counter = pbt.summary.count_error_types(graph)

barh(error_counter, plt)

"""
Explanation: The types of errors in a graph and their frequencies can be calculated using pbt.summary.count_error_types.
End of explanation
"""


incorrect_name_dict = pbt.summary.calculate_incorrect_name_dict(graph)

"""
Explanation: A common type of error is to use names that aren't contained within a namespace. These are thrown with pybel.parser.parse_exceptions.MissingNamespaceNameWarning. The PyBEL Tools function pbt.summary.calculate_incorrect_name_dict creates a dictionary for each namespace which incorrect names were used and their frequencies.
End of explanation
"""


barh(pbt.utils.count_dict_values(incorrect_name_dict), plt)

"""
Explanation: Using pbt.utils.count_dict_values, the number of unique incorrect names for each namespace is extracted and plotted.
End of explanation
"""


naked_names = pbt.summary.count_naked_names(graph)

"""
Explanation: Another common error is to write identifiers without a namespace, or for short, a naked name. The function  pbt.summary.count_naked_names returns a counter of how many time each naked name appeared.
End of explanation
"""


len(naked_names)

"""
Explanation: The number of unique naked names can be directly calculated with len() on the resulting counter from pbt.summary.count_naked_names.
End of explanation
"""


naked_names.most_common(25)

"""
Explanation: The 25 most common naked names are output below.
End of explanation
"""


error_groups = pbt.summary.group_errors(graph)

error_group_counts = Counter({k:len(v) for k,v in error_groups.items()})
error_group_counts.most_common(24)

"""
Explanation: Overall, the same error made multiple times is grouped to identify the most frequent errors.
End of explanation
"""


size_by_subgraph = pbt.summary.count_annotation_values(graph, 'Subgraph')

plt.figure(figsize=(10, 3))
barv(dict(size_by_subgraph.most_common(30)), plt)
plt.yscale('log')
plt.title('Top 30 Subgraph Sizes')
plt.show()

"""
Explanation: Error Analysis by Annotation
It might be useful to group the errors by a certain annotation/value pair. In these examples, the NeuroMMSig Database subgraph annotations are used. Ultimately, the error frequency will be compared to the size of each subgraph.
First, the sizes of the top 30 largest subgraphs are shown below.
End of explanation
"""


error_by_subgraph = pbt.summary.calculate_error_by_annotation(graph, 'Subgraph')

"""
Explanation: The list of all errors for each subgraph can be calculated with pbt.summary.calculate_error_by_annotation.
End of explanation
"""


error_by_subgraph_count = pbt.utils.count_dict_values(error_by_subgraph)

plt.figure(figsize=(10, 3))
barv(dict(error_by_subgraph_count.most_common(30)), plt)
plt.yscale('log')
plt.ylabel('Errors')
plt.title('Top 30 Most Error-Prone Subgraphs')
plt.show()

"""
Explanation: These data are aggregated to a count of the number of items in each list with pbt.utils.count_dict_values. The top 30 most error-prone subgraphs are shown below.
End of explanation
"""


subgraphs = sorted(size_by_subgraph)
df_data = [(size_by_subgraph[k], error_by_subgraph_count[k], error_by_subgraph_count[k] / size_by_subgraph[k]) for k in subgraphs]

df = pd.DataFrame(df_data, index=subgraphs, columns=['Size', 'Errors', 'E/S Ratio'])

df.to_csv('~/Desktop/errors.tsv', sep='\t')
df.sort_values('E/S Ratio', ascending=False).head(25)

"""
Explanation: Finally, the size to error ratio is calculated for each subgraph below. The 25 subgraphs with the highest error to size ratio are shown.
End of explanation
"""


sns.lmplot('Size', 'Errors', data=df)
plt.title('BEL Errors as a function of Subgraph Size')
plt.show()

"""
Explanation: The overall distribution of subgraph sizes and error counts is shown below. It indicates that they trend with a positive correlation, but there are clear outliers showing careful curation for large subgraphs, and sloppy curation for smaller ones.
End of explanation
"""


