import pandas as pd
import numpy as np
import requests
import io
from sklearn.covariance import MinCovDet
from scipy.spatial.distance import cdist
import os
import time
import itertools
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
[docs]def compute_distance(X):
'''Compute the Mahalanobis distance between pairwise combinations of all samples in X.
Args:
X (pd.DataFrame): DataFrame with spatial profile data
Returns:
dists (pd.DataFrame): All pairwise distances between samples. The index from X will become the index and columns for this DataFrame.
Note that this function will calculate pairwise distances for all combinations of samples in the index (e.g. it returns an n x n DataFrame, which can become quite large depending on the input data)
'''
mincovdet = MinCovDet(random_state=17)
vi = mincovdet.fit(X.values).covariance_
dists = cdist(X.values, X.values, 'mahalanobis', VI=np.linalg.inv(vi))
dists = np.triu(dists, k=1)
idx = X.index.copy()
idx.names = ['{}_A'.format(n) for n in X.index.names]
cols = X.index.copy()
cols.names = ['{}_B'.format(n) for n in X.index.names]
dists = pd.DataFrame(dists, index = idx, columns = cols)
return dists.where(dists!=0, np.nan)
[docs]def compute_fpr(x, y):
'''Compute false positive rates using x (true positive) and y (true negative) for values ranging between min(x.min(), y.min()) and max(x.max(), y.max())
Args:
x (pd.Series): True postive pairwise distances
y (pd.Series): True negative pariwise distances
Returns:
fpr (pd.Series): false positive rates for an array of distances ranging from min(x.min(), y.min()) to max(x.max(), y.max())
'''
res = {}
for i in np.linspace(min((y.min(), x.min())), max(y.max(), x.max()), 100):
tp = x[x<=i].groupby(['condition_A', 'condition_B']).size()
tn = y[y>i].groupby(['condition_A', 'condition_B']).size()
fp = y[y<=i].groupby(['condition_A', 'condition_B']).size()
fn = x[x>i].groupby(['condition_A', 'condition_B']).size()
res[i] = pd.concat([tp, tn, fp, fn], axis=1, keys = ['tp', 'tn', 'fp', 'fn'])
res = pd.concat(res, names = ['distance']).dropna()
fpr =(res['fp']/(res['fp']+res['tn']))
return fpr
[docs]class GetSTRINGInteractions:
'''Retrieve known interactions from the STRING database using their REST API
Attributes:
None
'''
def __init__(self):
'''Initialize object
'''
pass
[docs] def to_query_string(self, mylist, sep): #can also accept arrays
'''Convert a list to a string that can be used as a query string in an http post request
Args:
mylist (list): list of values
sep (str): separator for concatentating the values
Returns:
l (str): items in mylist concatenated into a single string
'''
l = ''
for item in mylist:
try:
l = l + str(item) + sep
except TypeError: # exception to deal with NaNs in mylist
pass
return l
[docs] def map_identifiers_string(self, proteins, species):
'''Use STRING's API to retrive the corresponding STRING identifiers for each protein
Args:
proteins (Union(list, np.ndarray)): Uniprot protein accessions to be mapped to StringIDs
species (str): Taxonomic identifier for the given protein species (e.g. '9606' for Homo Sapiens)
Returns:
df (pd.DataFrame): Uniprot accessions mapped to their corresponding StringIDs
'''
# STRING will only let you query 2000 proteins at a time, otherwise you get an error message back
if len(proteins) >= 2000:
n_chunks = int(np.ceil(len(proteins)/2000))
dfs = []
for chunk in range(n_chunks):
ps = proteins[2000*chunk:2000*(chunk+1)]
p = self.to_query_string(ps, '%0D') #each protein on a new line
url = 'https://string-db.org/api/tsv/get_string_ids'
params = {'identifiers': p, 'species':species, 'echo_query': 1, 'caller_identity': 'TRANSPIRE'}
r = requests.post(url, data = params)
_df = pd.read_csv(io.StringIO(r.text), sep = '\t', header = 0, index_col = None)
dfs.append(_df)
time.sleep(1)
df = pd.concat(dfs, axis = 0, join = 'outer')
else:
ps = proteins
p = self.to_query_string(ps, '%0D') #each protein on a new line
url = 'https://string-db.org/api/tsv/get_string_ids'
params = {'identifiers': p, 'species':species, 'echo_query': 1, 'caller_identity': 'Princeton_University'}
r = requests.post(url, data = params)
df = pd.read_csv(io.StringIO(r.text), sep = '\t', header = 0, index_col = None)
df = df[['stringId', 'queryItem']].set_index('stringId')
return df
[docs] def get_interactions(self, IDs, species):
'''Query STRING database for known interactions between proteins
Args:
IDs (Union(list, np.ndarray)): StringIDs for query proteins
species (str): Taxonomic identifier for the given protein species (e.g. '9606' for Homo Sapiens)
Returns:
df (pd.DataFrame): known interactions between proteins as well as their corresponding STRING data (evidence scores, etc.)
'''
# STRING will only let you query 2000 proteins at a time
if len(IDs) > 2000:
n_chunks = int(np.ceil(len(IDs)/2000))
dfs = []
for chunk in range(n_chunks):
ID_list = IDs[2000*chunk:2000*(chunk+1)]
p = self.to_query_string(ID_list, '%0D') #each ID on a new line
url = 'https://string-db.org/api/tsv/network'
params = {'identifiers': p, 'species':species, 'caller_identity': 'Princeton_University'}
r = requests.post(url, data = params)
_df = pd.read_csv(io.StringIO(r.text), sep = '\t', header = 0, index_col = None)
dfs.append(_df)
time.sleep(1)
df = pd.concat(dfs, axis = 0, join = 'outer')
else:
ID_list = IDs
p = self.to_query_string(ID_list, '%0D') #each ID on a new line
url = 'https://string-db.org/api/tsv/network'
params = {'identifiers': p, 'species':species, 'caller_identity': 'Princeton_University'}
r = requests.post(url, data = params)
df = pd.read_csv(io.StringIO(r.text), sep = '\t', header = 0, index_col = None)
return df
[docs] def query(self, proteins, species, score_cutoff):
'''Perform a STRING database query on a given set of protein accession numbers.
This is a simple wrapper combining several GetSTRINGInteractions methods that returns at DataFrame of known interactions between the input proteins.
Args:
proteins (np.ndarray): Uniprot accession numbers for proteins to query for known interactions
species (str): Taxonomic identifier for the given protein species (e.g. '9606' for Homo Sapiens)
score_cutoff (float): STRING score cutoff for the returned iteractions
Returns:
interactions (pd.DataFrame): Known iteractions bewteen the input proteins
'''
string_IDs = self.map_identifiers_string(proteins.tolist(), species)
string_IDs = string_IDs[~string_IDs.squeeze().index.duplicated()]
interactions_ = self.get_interactions(string_IDs.index.values.tolist(), species)
interactions = interactions_.copy()
interactions['Accession_A'] = string_IDs.loc[species+'.'+interactions_['stringId_A'], 'queryItem'].values
interactions['Accession_B'] = string_IDs.loc[species+'.'+interactions_['stringId_B'], 'queryItem'].values
interactions = interactions.set_index(['Accession_A', 'Accession_B'])
interactions = interactions[~interactions.index.duplicated()]
interactions = interactions[interactions['score']>=score_cutoff]
# create a copy of values for when the indices are reversed
interactions_copy = interactions.copy()
interactions_copy.index = pd.MultiIndex.from_tuples(list(zip(interactions.index.get_level_values('Accession_B'), interactions.index.get_level_values('Accession_A'))), names = interactions.index.names)
temp = pd.concat([interactions, interactions_copy])
interactions = temp[~temp.index.duplicated()]
return interactions