From 4204c1cc990b37cd643822e5fdc5d6612a7fef85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Fri, 22 Nov 2024 13:20:02 +0000 Subject: [PATCH 01/20] fix devtainer --- .devcontainer/devcontainer.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5a319b5..d8132b4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,22 +1,22 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/python { - "name": "vpipe-frontend", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye" + "name": "vpipe-frontend", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [8000], - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "pip3 install --user -r requirements.txt", + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "pip3 install --user -r requirements.txt", - // Configure tool-specific properties. - // "customizations": {}, + // Configure tool-specific properties. + // "customizations": {}, - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" } From 76bf4dca5450d23f97346ea04c4c950a4e20fce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Fri, 22 Nov 2024 13:27:01 +0000 Subject: [PATCH 02/20] fix devtainer --- .devcontainer/devcontainer.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d8132b4..d461870 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,6 +13,13 @@ // Use 'postCreateCommand' to run commands after the container is created. "postCreateCommand": "pip3 install --user -r requirements.txt", + "customizations": { + "vscode": { + "settings": { + "editor.rulers": [80] + } + } + } // Configure tool-specific properties. // "customizations": {}, From cb37735dfc8b3eabc45875d57852ab7cac0b99d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Fri, 22 Nov 2024 15:52:17 +0100 Subject: [PATCH 03/20] load muation names --- app.py | 2 ++ resistance_mut.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 resistance_mut.py diff --git a/app.py b/app.py index 8d18d6a..8d0fb4c 100644 --- a/app.py +++ b/app.py @@ -2,11 +2,13 @@ import index import mutation_freq import variant_deconv +import resistance_mut PAGES = { "Home": {"module": index}, "Mutation Frequency": {"module": mutation_freq}, "Variant Deconvolution": {"module": variant_deconv}, + "Resistance Mutations": {"module": resistance_mut} } def sidebar(): diff --git a/resistance_mut.py b/resistance_mut.py new file mode 100644 index 0000000..4a19940 --- /dev/null +++ b/resistance_mut.py @@ -0,0 +1,38 @@ +import streamlit as st +import time +import requests +from PIL import Image +from io import BytesIO +import base64 +import yaml +import pandas as pd + +# Load configuration from config.yaml +with open('config.yaml', 'r') as file: + config = yaml.safe_load(file) + +server_ip = config.get('server', {}).get('ip_address', 'http://default_ip:8000') + +def app(): + st.title("Resistance Mutations") + + st.write("This page allows you to run the Lollipop variant deconvolution tool with a custom variant definitions.") + + # have three buttons for the three sets of mutaitons + # data/3CLpro_inhibitors_datasheet.csv + # data/RdRP_inhibitors_datasheet.csv + # data/spike_mAbs_datasheet.csv + st.write("Select from the following resistance mutation sets:") + + options = ["3CLpro Inhibitors", "RdRP Inhibitors", "Spike mAbs"] + selected_option = st.selectbox("Select a resistance mutation set:", options) + + + if selected_option == "3CLpro Inhibitors": + df = pd.read_csv('data/3CLpro_inhibitors_datasheet.csv') + elif selected_option == "RdRP Inhibitors": + df = pd.read_csv('data/RdRP_inhibitors_datasheet.csv') + elif selected_option == "Spike mAbs": + df = pd.read_csv('data/spike_mAbs_datasheet.csv') + + st.write(df) From b144810b86e36a5e881d448e6dcb422d6ce59f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Fri, 22 Nov 2024 16:22:50 +0100 Subject: [PATCH 04/20] first request to silo --- resistance_mut.py | 49 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index 4a19940..e6e6666 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -18,21 +18,46 @@ def app(): st.write("This page allows you to run the Lollipop variant deconvolution tool with a custom variant definitions.") - # have three buttons for the three sets of mutaitons - # data/3CLpro_inhibitors_datasheet.csv - # data/RdRP_inhibitors_datasheet.csv - # data/spike_mAbs_datasheet.csv + # have three buttons for the three sets of mutations st.write("Select from the following resistance mutation sets:") - options = ["3CLpro Inhibitors", "RdRP Inhibitors", "Spike mAbs"] - selected_option = st.selectbox("Select a resistance mutation set:", options) + # TODO: currently hardcoded, should be fetched from the server + options = { + "3CLpro Inhibitors": 'data/3CLpro_inhibitors_datasheet.csv', + "RdRP Inhibitors": 'data/RdRP_inhibitors_datasheet.csv', + "Spike mAbs": 'data/spike_mAbs_datasheet.csv' + } + selected_option = st.selectbox("Select a resistance mutation set:", options.keys()) - if selected_option == "3CLpro Inhibitors": - df = pd.read_csv('data/3CLpro_inhibitors_datasheet.csv') - elif selected_option == "RdRP Inhibitors": - df = pd.read_csv('data/RdRP_inhibitors_datasheet.csv') - elif selected_option == "Spike mAbs": - df = pd.read_csv('data/spike_mAbs_datasheet.csv') + df = pd.read_csv(options[selected_option]) st.write(df) + + # Define the POST request payload + payload = { + "aminoAcidMutations": ["S:144L"], + "dateFrom": ["2022-02-18"], + "dateTo": ["2024-11-01"], + "fields": ["date"] + } + + # Make the POST request + response = requests.post( + 'https://lapis.cov-spectrum.org/open/v2/sample/aggregated', + headers={ + 'accept': 'application/json', + 'Content-Type': 'application/json' + }, + json=payload + ) + + # Check if the request was successful + if response.status_code == 200: + data = response.json() + st.write("Data fetched from the server:") + st.write(data) + else: + st.write("Failed to fetch data from the server.") + st.write(f"Status code: {response.status_code}") + st.write(response.text) From 580d92d812e0ed3cc91df6a3dc6ffe6f8ab1407c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Fri, 22 Nov 2024 16:26:00 +0100 Subject: [PATCH 05/20] allow users choice for daterange --- resistance_mut.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index e6e6666..c7c83e5 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -31,14 +31,18 @@ def app(): selected_option = st.selectbox("Select a resistance mutation set:", options.keys()) df = pd.read_csv(options[selected_option]) + + # allow the user to chose a date range + st.write("Select a date range:") + date_range = st.date_input("Select a date range:", [pd.to_datetime("2022-01-01"), pd.to_datetime("2024-01-01")]) st.write(df) # Define the POST request payload payload = { "aminoAcidMutations": ["S:144L"], - "dateFrom": ["2022-02-18"], - "dateTo": ["2024-11-01"], + "dateFrom": date_range[0].strftime("%Y-%m-%d"), + "dateTo": date_range[1].strftime("%Y-%m-%d"), "fields": ["date"] } From 8ab5d7c3137e391901264656329353dea07b1548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Fri, 22 Nov 2024 16:42:22 +0100 Subject: [PATCH 06/20] first requests --- resistance_mut.py | 87 ++++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index c7c83e5..9355cb5 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -1,9 +1,5 @@ import streamlit as st -import time import requests -from PIL import Image -from io import BytesIO -import base64 import yaml import pandas as pd @@ -13,12 +9,36 @@ server_ip = config.get('server', {}).get('ip_address', 'http://default_ip:8000') +def fetch_data(mutation, date_range): + payload = { + "aminoAcidMutations": [mutation], + "dateFrom": date_range[0].strftime('%Y-%m-%d'), + "dateTo": date_range[1].strftime('%Y-%m-%d'), + "fields": ["date"] + } + + response = requests.post( + 'https://lapis.cov-spectrum.org/open/v2/sample/aggregated', + headers={ + 'accept': 'application/json', + 'Content-Type': 'application/json' + }, + json=payload + ) + + if response.status_code == 200: + return response.json() + else: + st.write(f"Failed to fetch data for mutation {mutation}.") + st.write(f"Status code: {response.status_code}") + st.write(response.text) + return None + def app(): st.title("Resistance Mutations") - st.write("This page allows you to run the Lollipop variant deconvolution tool with a custom variant definitions.") + st.write("This page allows you to run the Lollipop variant deconvolution tool with custom variant definitions.") - # have three buttons for the three sets of mutations st.write("Select from the following resistance mutation sets:") # TODO: currently hardcoded, should be fetched from the server @@ -31,37 +51,34 @@ def app(): selected_option = st.selectbox("Select a resistance mutation set:", options.keys()) df = pd.read_csv(options[selected_option]) - - # allow the user to chose a date range + + # Get the list of mutations for the selected set + mutations = df['Mutation'].tolist() + # Lambda function to format the mutation list, from S24L to S:24L + format_mutation = lambda x: f"{x[0]}:{x[1:]}" + # Apply the lambda function to each element in the mutations list + formatted_mutations = [format_mutation(mutation) for mutation in mutations] + + st.write(f"Selected mutations: {formatted_mutations}") + + # Allow the user to choose a date range st.write("Select a date range:") date_range = st.date_input("Select a date range:", [pd.to_datetime("2022-01-01"), pd.to_datetime("2024-01-01")]) - st.write(df) + if st.button("Fetch Data"): + all_data = [] + for mutation in formatted_mutations: + data = fetch_data(mutation, date_range) + if data: + all_data.append(data) - # Define the POST request payload - payload = { - "aminoAcidMutations": ["S:144L"], - "dateFrom": date_range[0].strftime("%Y-%m-%d"), - "dateTo": date_range[1].strftime("%Y-%m-%d"), - "fields": ["date"] - } - - # Make the POST request - response = requests.post( - 'https://lapis.cov-spectrum.org/open/v2/sample/aggregated', - headers={ - 'accept': 'application/json', - 'Content-Type': 'application/json' - }, - json=payload - ) + # Display all collected data + if all_data: + st.write("Data fetched from the server:") + for data in all_data: + st.write(data) + else: + st.write("No data found for the given mutations.") - # Check if the request was successful - if response.status_code == 200: - data = response.json() - st.write("Data fetched from the server:") - st.write(data) - else: - st.write("Failed to fetch data from the server.") - st.write(f"Status code: {response.status_code}") - st.write(response.text) +if __name__ == "__main__": + app() \ No newline at end of file From 31d5ce053b54c21dbf21e360780f757380f7de9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Fri, 22 Nov 2024 16:46:31 +0100 Subject: [PATCH 07/20] add resistance mutation query outcome --- resistance_mut.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index 9355cb5..b87ab08 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -2,6 +2,7 @@ import requests import yaml import pandas as pd +import logging # Load configuration from config.yaml with open('config.yaml', 'r') as file: @@ -29,9 +30,9 @@ def fetch_data(mutation, date_range): if response.status_code == 200: return response.json() else: - st.write(f"Failed to fetch data for mutation {mutation}.") - st.write(f"Status code: {response.status_code}") - st.write(response.text) + logging.error(f"Failed to fetch data for mutation {mutation}.") + logging.error(f"Status code: {response.status_code}") + logging.error(response.text) return None def app(): @@ -67,14 +68,16 @@ def app(): if st.button("Fetch Data"): all_data = [] + successful_fetches = 0 for mutation in formatted_mutations: data = fetch_data(mutation, date_range) if data: all_data.append(data) + successful_fetches += 1 # Display all collected data if all_data: - st.write("Data fetched from the server:") + st.write(f"{successful_fetches} Mutations of {len(formatted_mutations)} found on Cov-Spectrum") for data in all_data: st.write(data) else: From c1d8f36573d2d91d641b5ad8fca16cd837f8776f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Fri, 22 Nov 2024 17:11:37 +0100 Subject: [PATCH 08/20] resistance mutation lookups --- resistance_mut.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index b87ab08..9e94b52 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -67,21 +67,17 @@ def app(): date_range = st.date_input("Select a date range:", [pd.to_datetime("2022-01-01"), pd.to_datetime("2024-01-01")]) if st.button("Fetch Data"): - all_data = [] + all_data = {} successful_fetches = 0 for mutation in formatted_mutations: data = fetch_data(mutation, date_range) if data: - all_data.append(data) + all_data[mutation] = data successful_fetches += 1 + + st.write(all_data) - # Display all collected data - if all_data: - st.write(f"{successful_fetches} Mutations of {len(formatted_mutations)} found on Cov-Spectrum") - for data in all_data: - st.write(data) - else: - st.write("No data found for the given mutations.") + # next make df out of it / catch the case where change was found but no data was returned if __name__ == "__main__": app() \ No newline at end of file From c20725ee54f671284eef7102b2f21d96efb0a5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 10:23:22 +0100 Subject: [PATCH 09/20] make async --- requirements.txt | 3 ++- resistance_mut.py | 51 +++++++++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8726bd1..2c0a694 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ s3fs st-files-connection seaborn matplotlib -pillow \ No newline at end of file +pillow +aiohttp \ No newline at end of file diff --git a/resistance_mut.py b/resistance_mut.py index 9e94b52..c0a3ce9 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -3,6 +3,8 @@ import yaml import pandas as pd import logging +import aiohttp +import asyncio # Load configuration from config.yaml with open('config.yaml', 'r') as file: @@ -10,7 +12,7 @@ server_ip = config.get('server', {}).get('ip_address', 'http://default_ip:8000') -def fetch_data(mutation, date_range): +async def fetch_data(session, mutation, date_range): payload = { "aminoAcidMutations": [mutation], "dateFrom": date_range[0].strftime('%Y-%m-%d'), @@ -18,22 +20,26 @@ def fetch_data(mutation, date_range): "fields": ["date"] } - response = requests.post( + async with session.post( 'https://lapis.cov-spectrum.org/open/v2/sample/aggregated', headers={ 'accept': 'application/json', 'Content-Type': 'application/json' }, json=payload - ) - - if response.status_code == 200: - return response.json() - else: - logging.error(f"Failed to fetch data for mutation {mutation}.") - logging.error(f"Status code: {response.status_code}") - logging.error(response.text) - return None + ) as response: + if response.status == 200: + return await response.json() + else: + logging.error(f"Failed to fetch data for mutation {mutation}.") + logging.error(f"Status code: {response.status}") + logging.error(await response.text()) + return None + +async def fetch_all_data(mutations, date_range): + async with aiohttp.ClientSession() as session: + tasks = [fetch_data(session, mutation, date_range) for mutation in mutations] + return await asyncio.gather(*tasks) def app(): st.title("Resistance Mutations") @@ -67,17 +73,18 @@ def app(): date_range = st.date_input("Select a date range:", [pd.to_datetime("2022-01-01"), pd.to_datetime("2024-01-01")]) if st.button("Fetch Data"): - all_data = {} - successful_fetches = 0 - for mutation in formatted_mutations: - data = fetch_data(mutation, date_range) - if data: - all_data[mutation] = data - successful_fetches += 1 - - st.write(all_data) - - # next make df out of it / catch the case where change was found but no data was returned + all_data = asyncio.run(fetch_all_data(formatted_mutations, date_range)) + + # Filter out None values (failed fetches) + all_data = [data for data in all_data if data] + + # Display all collected data + if all_data: + st.write("Data fetched from the server:") + for data in all_data: + st.write(data) + else: + st.write("No data found for the given mutations.") if __name__ == "__main__": app() \ No newline at end of file From fb357234bd5d35b431d2e4f3c022fe6c96fc46dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 10:50:45 +0100 Subject: [PATCH 10/20] enricht mutation nae --- resistance_mut.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index c0a3ce9..78fc4d3 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -1,3 +1,4 @@ +import json import streamlit as st import requests import yaml @@ -29,12 +30,15 @@ async def fetch_data(session, mutation, date_range): json=payload ) as response: if response.status == 200: - return await response.json() + data = await response.json() + return {"mutation": mutation, + "data": data.get('data', [])} else: logging.error(f"Failed to fetch data for mutation {mutation}.") logging.error(f"Status code: {response.status}") logging.error(await response.text()) - return None + return {"mutation": mutation, + "data": None} async def fetch_all_data(mutations, date_range): async with aiohttp.ClientSession() as session: @@ -75,8 +79,8 @@ def app(): if st.button("Fetch Data"): all_data = asyncio.run(fetch_all_data(formatted_mutations, date_range)) - # Filter out None values (failed fetches) - all_data = [data for data in all_data if data] + # filter out mutations with no data + all_data = [data for data in all_data if data['data']] # Display all collected data if all_data: @@ -84,7 +88,7 @@ def app(): for data in all_data: st.write(data) else: - st.write("No data found for the given mutations.") + st.write("No data found for the given mutations.") if __name__ == "__main__": app() \ No newline at end of file From 90abeee54de634565667b95b79f4d23e100a3968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 10:53:59 +0100 Subject: [PATCH 11/20] surcessful wragneling --- resistance_mut.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index 78fc4d3..5be5dbf 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -82,13 +82,17 @@ def app(): # filter out mutations with no data all_data = [data for data in all_data if data['data']] - # Display all collected data - if all_data: - st.write("Data fetched from the server:") - for data in all_data: - st.write(data) - else: - st.write("No data found for the given mutations.") + # build a dataframe from the collected data + # for each mutation make a row + # iterate through the data and add a column for each date with the number of samples + mutation_data = [] + for data in all_data: + mutation = data['mutation'] + mutation_data.append([mutation] + [d['count'] for d in data['data']]) + + st.write("Data fetched from the server:") + df = pd.DataFrame(mutation_data, columns=['Mutation'] + [d['date'] for d in all_data[0]['data']]) + st.write(df) if __name__ == "__main__": app() \ No newline at end of file From 6158339cef0a60f086e85e074c2506f0327ec674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 11:09:53 +0100 Subject: [PATCH 12/20] first plots --- resistance_mut.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/resistance_mut.py b/resistance_mut.py index 5be5dbf..63b2df7 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -1,4 +1,6 @@ import json +from matplotlib import pyplot as plt +import numpy as np import streamlit as st import requests import yaml @@ -7,6 +9,7 @@ import aiohttp import asyncio + # Load configuration from config.yaml with open('config.yaml', 'r') as file: config = yaml.safe_load(file) @@ -44,6 +47,39 @@ async def fetch_all_data(mutations, date_range): async with aiohttp.ClientSession() as session: tasks = [fetch_data(session, mutation, date_range) for mutation in mutations] return await asyncio.gather(*tasks) + +def plot_heatmap(df): + + # get the dates from the columns + dates = df.columns + + # get the mutations from the index + mutations = df.index.tolist() + + # 2. Heatmap Construction + fig, ax = plt.subplots(figsize=(12, 8)) + # Replace None with np.nan and remove commas from numbers + df = df.replace({None: np.nan, ',': ''}, regex=True).astype(float) + + # Create a colormap with a custom color for NaN values + cmap = plt.cm.Blues + cmap.set_bad(color='lightcoral') # Set NaN values to light rose color + + im = ax.imshow(df.values, cmap=cmap) # Use the custom colormap + + # Set axis labels + ax.set_xticks([0, len(dates) // 2, len(dates) - 1]) + ax.set_xticklabels([dates[0], dates[len(dates) // 2], dates[-1]], rotation=45) + ax.set_yticks(np.arange(len(mutations))) + ax.set_yticklabels(mutations, fontsize=8) + + # Add colorbar + cbar = ax.figure.colorbar(im, ax=ax) + cbar.ax.set_ylabel("Occurrence Frequency", rotation=-90, va="bottom") + + return fig + + def app(): st.title("Resistance Mutations") @@ -92,7 +128,15 @@ def app(): st.write("Data fetched from the server:") df = pd.DataFrame(mutation_data, columns=['Mutation'] + [d['date'] for d in all_data[0]['data']]) + # Set the first column as the index + df.set_index(df.columns[0], inplace=True) st.write(df) + st.write("df.values:", df.values) + + # Plot the heatmap + fig = plot_heatmap(df) + st.pyplot(fig) + if __name__ == "__main__": app() \ No newline at end of file From b38b3b22cae6972669ee6139792bada441af7ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 11:18:44 +0100 Subject: [PATCH 13/20] refactor --- resistance_mut.py | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index 63b2df7..9b20f8c 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -48,6 +48,27 @@ async def fetch_all_data(mutations, date_range): tasks = [fetch_data(session, mutation, date_range) for mutation in mutations] return await asyncio.gather(*tasks) +def fetch_reformat_data(formatted_mutations, date_range): + all_data = asyncio.run(fetch_all_data(formatted_mutations, date_range)) + + # filter out mutations with no data + all_data = [data for data in all_data if data['data']] + + # build a dataframe from the collected data + # for each mutation make a row + # iterate through the data and add a column for each date with the number of samples + mutation_data = [] + for data in all_data: + mutation = data['mutation'] + mutation_data.append([mutation] + [d['count'] for d in data['data']]) + + st.write("Data fetched from the server:") + df = pd.DataFrame(mutation_data, columns=['Mutation'] + [d['date'] for d in all_data[0]['data']]) + # Set the first column as the index + df.set_index(df.columns[0], inplace=True) + return df + + def plot_heatmap(df): # get the dates from the columns @@ -113,26 +134,7 @@ def app(): date_range = st.date_input("Select a date range:", [pd.to_datetime("2022-01-01"), pd.to_datetime("2024-01-01")]) if st.button("Fetch Data"): - all_data = asyncio.run(fetch_all_data(formatted_mutations, date_range)) - - # filter out mutations with no data - all_data = [data for data in all_data if data['data']] - - # build a dataframe from the collected data - # for each mutation make a row - # iterate through the data and add a column for each date with the number of samples - mutation_data = [] - for data in all_data: - mutation = data['mutation'] - mutation_data.append([mutation] + [d['count'] for d in data['data']]) - - st.write("Data fetched from the server:") - df = pd.DataFrame(mutation_data, columns=['Mutation'] + [d['date'] for d in all_data[0]['data']]) - # Set the first column as the index - df.set_index(df.columns[0], inplace=True) - st.write(df) - - st.write("df.values:", df.values) + df = fetch_reformat_data(formatted_mutations, date_range) # Plot the heatmap fig = plot_heatmap(df) From 51dd6cefef026a03231132ebe2e0b4de866b33cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 12:02:58 +0100 Subject: [PATCH 14/20] seboard --- resistance_mut.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index 9b20f8c..92a46b5 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -8,6 +8,7 @@ import logging import aiohttp import asyncio +import seaborn as sns # Load configuration from config.yaml @@ -70,33 +71,24 @@ def fetch_reformat_data(formatted_mutations, date_range): def plot_heatmap(df): - - # get the dates from the columns - dates = df.columns - - # get the mutations from the index - mutations = df.index.tolist() - - # 2. Heatmap Construction - fig, ax = plt.subplots(figsize=(12, 8)) # Replace None with np.nan and remove commas from numbers df = df.replace({None: np.nan, ',': ''}, regex=True).astype(float) - + # Create a colormap with a custom color for NaN values - cmap = plt.cm.Blues + cmap = sns.color_palette("Blues", as_cmap=True) cmap.set_bad(color='lightcoral') # Set NaN values to light rose color - im = ax.imshow(df.values, cmap=cmap) # Use the custom colormap + # Plot the heatmap + fig, ax = plt.subplots(figsize=(12, 8)) + annot = True if df.shape[0] * df.shape[1] <= 100 else False # Annotate only if the plot is small enough + sns.heatmap(df, cmap=cmap, ax=ax, cbar_kws={'label': 'Occurrence Frequency'}, + linewidths=.5, linecolor='lightgrey', annot=annot, fmt=".1f", + annot_kws={"size": 8}, mask=df.isnull(), cbar=True) # Set axis labels - ax.set_xticks([0, len(dates) // 2, len(dates) - 1]) - ax.set_xticklabels([dates[0], dates[len(dates) // 2], dates[-1]], rotation=45) - ax.set_yticks(np.arange(len(mutations))) - ax.set_yticklabels(mutations, fontsize=8) - - # Add colorbar - cbar = ax.figure.colorbar(im, ax=ax) - cbar.ax.set_ylabel("Occurrence Frequency", rotation=-90, va="bottom") + ax.set_xticks([0, len(df.columns) // 2, len(df.columns) - 1]) + ax.set_xticklabels([df.columns[0], df.columns[len(df.columns) // 2], df.columns[-1]], rotation=45) + ax.set_yticklabels(df.index.tolist(), fontsize=10, rotation=0) # Rotate mutation labels to be horizontal return fig From ee197da7548b39fb91819495f980443001993724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 12:31:41 +0100 Subject: [PATCH 15/20] fix population --- resistance_mut.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index 92a46b5..ef3cc74 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -52,21 +52,26 @@ async def fetch_all_data(mutations, date_range): def fetch_reformat_data(formatted_mutations, date_range): all_data = asyncio.run(fetch_all_data(formatted_mutations, date_range)) - # filter out mutations with no data - all_data = [data for data in all_data if data['data']] + # get all unique dates + dates = set() + for data in all_data: + if data['data']: + for d in data['data']: + dates.add(d['date']) + + print(dates) + print(len(dates)) + + # make a dataframe with the dates as columns and the mutations as rows + df = pd.DataFrame(index=formatted_mutations, columns=list(dates)) - # build a dataframe from the collected data - # for each mutation make a row - # iterate through the data and add a column for each date with the number of samples - mutation_data = [] + # fill the dataframe with the data for data in all_data: - mutation = data['mutation'] - mutation_data.append([mutation] + [d['count'] for d in data['data']]) - - st.write("Data fetched from the server:") - df = pd.DataFrame(mutation_data, columns=['Mutation'] + [d['date'] for d in all_data[0]['data']]) - # Set the first column as the index - df.set_index(df.columns[0], inplace=True) + if data['data']: + for d in data['data']: + df.at[data['mutation'], d['date']] = d['count'] + print(data['mutation'], d['date'], d['count']) + return df @@ -79,16 +84,16 @@ def plot_heatmap(df): cmap.set_bad(color='lightcoral') # Set NaN values to light rose color # Plot the heatmap - fig, ax = plt.subplots(figsize=(12, 8)) + fig, ax = plt.subplots(figsize=(15, 8)) annot = True if df.shape[0] * df.shape[1] <= 100 else False # Annotate only if the plot is small enough sns.heatmap(df, cmap=cmap, ax=ax, cbar_kws={'label': 'Occurrence Frequency'}, linewidths=.5, linecolor='lightgrey', annot=annot, fmt=".1f", - annot_kws={"size": 8}, mask=df.isnull(), cbar=True) + annot_kws={"size": 10}, mask=df.isnull(), cbar=True) # Set axis labels ax.set_xticks([0, len(df.columns) // 2, len(df.columns) - 1]) ax.set_xticklabels([df.columns[0], df.columns[len(df.columns) // 2], df.columns[-1]], rotation=45) - ax.set_yticklabels(df.index.tolist(), fontsize=10, rotation=0) # Rotate mutation labels to be horizontal + ax.set_yticklabels(df.index.tolist(), fontsize=12, rotation=0) # Rotate mutation labels to be horizontal return fig @@ -127,7 +132,7 @@ def app(): if st.button("Fetch Data"): df = fetch_reformat_data(formatted_mutations, date_range) - + st.write(df) # Plot the heatmap fig = plot_heatmap(df) st.pyplot(fig) From bdf7221cd5ad60d1892e3daf58d31c68aa16a3ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 13:46:46 +0100 Subject: [PATCH 16/20] fix plot --- resistance_mut.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index ef3cc74..6acf84c 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -52,15 +52,16 @@ async def fetch_all_data(mutations, date_range): def fetch_reformat_data(formatted_mutations, date_range): all_data = asyncio.run(fetch_all_data(formatted_mutations, date_range)) + # get dates from date_range + dates = pd.date_range(date_range[0], date_range[1]).strftime('%Y-%m-%d') + # get all unique dates - dates = set() - for data in all_data: - if data['data']: - for d in data['data']: - dates.add(d['date']) + # dates = set() + # for data in all_data: + # if data['data']: + # for d in data['data']: + # dates.add(d['date']) - print(dates) - print(len(dates)) # make a dataframe with the dates as columns and the mutations as rows df = pd.DataFrame(index=formatted_mutations, columns=list(dates)) @@ -70,7 +71,6 @@ def fetch_reformat_data(formatted_mutations, date_range): if data['data']: for d in data['data']: df.at[data['mutation'], d['date']] = d['count'] - print(data['mutation'], d['date'], d['count']) return df @@ -83,18 +83,18 @@ def plot_heatmap(df): cmap = sns.color_palette("Blues", as_cmap=True) cmap.set_bad(color='lightcoral') # Set NaN values to light rose color - # Plot the heatmap - fig, ax = plt.subplots(figsize=(15, 8)) + # Adjust the plot size based on the number of rows in the dataframe + height = max(8, len(df) * 0.3) # Minimum height of 8, with 0.5 units per row + fig, ax = plt.subplots(figsize=(15, height)) + annot = True if df.shape[0] * df.shape[1] <= 100 else False # Annotate only if the plot is small enough - sns.heatmap(df, cmap=cmap, ax=ax, cbar_kws={'label': 'Occurrence Frequency'}, + sns.heatmap(df, cmap=cmap, ax=ax, cbar_kws={'label': 'Occurrence Frequency', 'orientation': 'horizontal'}, linewidths=.5, linecolor='lightgrey', annot=annot, fmt=".1f", - annot_kws={"size": 10}, mask=df.isnull(), cbar=True) + annot_kws={"size": 10}, mask=df.isnull(), cbar=True, cbar_ax=fig.add_axes([0.15, 0.89, 0.7, 0.02])) # Set axis labels ax.set_xticks([0, len(df.columns) // 2, len(df.columns) - 1]) ax.set_xticklabels([df.columns[0], df.columns[len(df.columns) // 2], df.columns[-1]], rotation=45) - ax.set_yticklabels(df.index.tolist(), fontsize=12, rotation=0) # Rotate mutation labels to be horizontal - return fig @@ -124,15 +124,17 @@ def app(): # Apply the lambda function to each element in the mutations list formatted_mutations = [format_mutation(mutation) for mutation in mutations] - st.write(f"Selected mutations: {formatted_mutations}") + st.write(f"Selected mutations:") + st.write(formatted_mutations) + # Allow the user to choose a date range st.write("Select a date range:") date_range = st.date_input("Select a date range:", [pd.to_datetime("2022-01-01"), pd.to_datetime("2024-01-01")]) if st.button("Fetch Data"): + st.write("Fetching data...") df = fetch_reformat_data(formatted_mutations, date_range) - st.write(df) # Plot the heatmap fig = plot_heatmap(df) st.pyplot(fig) From f23dde05c1d20f476aae469e59415211d22b4ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 13:50:16 +0100 Subject: [PATCH 17/20] restantrce plots --- resistance_mut.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/resistance_mut.py b/resistance_mut.py index 6acf84c..0c4d5e9 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -81,7 +81,7 @@ def plot_heatmap(df): # Create a colormap with a custom color for NaN values cmap = sns.color_palette("Blues", as_cmap=True) - cmap.set_bad(color='lightcoral') # Set NaN values to light rose color + cmap.set_bad(color='#FFCCCC') # Set NaN values to a fainter red color # Adjust the plot size based on the number of rows in the dataframe height = max(8, len(df) * 0.3) # Minimum height of 8, with 0.5 units per row @@ -90,7 +90,7 @@ def plot_heatmap(df): annot = True if df.shape[0] * df.shape[1] <= 100 else False # Annotate only if the plot is small enough sns.heatmap(df, cmap=cmap, ax=ax, cbar_kws={'label': 'Occurrence Frequency', 'orientation': 'horizontal'}, linewidths=.5, linecolor='lightgrey', annot=annot, fmt=".1f", - annot_kws={"size": 10}, mask=df.isnull(), cbar=True, cbar_ax=fig.add_axes([0.15, 0.89, 0.7, 0.02])) + annot_kws={"size": 10}, mask=df.isnull(), cbar=True, cbar_ax=fig.add_axes([0.15, 0.90, 0.7, 0.02])) # Set axis labels ax.set_xticks([0, len(df.columns) // 2, len(df.columns) - 1]) @@ -135,9 +135,14 @@ def app(): if st.button("Fetch Data"): st.write("Fetching data...") df = fetch_reformat_data(formatted_mutations, date_range) - # Plot the heatmap - fig = plot_heatmap(df) - st.pyplot(fig) + + # Check if the dataframe is all NaN + if df.isnull().all().all(): + st.error("The fetched data contains only NaN values. Please try a different date range or mutation set.") + else: + # Plot the heatmap + fig = plot_heatmap(df) + st.pyplot(fig) if __name__ == "__main__": app() \ No newline at end of file From d0fafe80d605624f730fbcca80042791b9afdaf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 14:04:21 +0100 Subject: [PATCH 18/20] forgot the datafiles --- data/3CLpro_inhibitors_datasheet.csv | 72 ++++++++++++ data/RdRP_inhibitors_datasheet.csv | 20 ++++ data/spike_mAbs_datasheet.csv | 165 +++++++++++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 data/3CLpro_inhibitors_datasheet.csv create mode 100644 data/RdRP_inhibitors_datasheet.csv create mode 100644 data/spike_mAbs_datasheet.csv diff --git a/data/3CLpro_inhibitors_datasheet.csv b/data/3CLpro_inhibitors_datasheet.csv new file mode 100644 index 0000000..d7f787f --- /dev/null +++ b/data/3CLpro_inhibitors_datasheet.csv @@ -0,0 +1,72 @@ +Mutation,NTV: fold,NTV: pocket,ENS: fold,fitness,in patient,in vitro,Prevalence +T21I,1.5,,1.6,1.6,,12,0.1% +T25A,12.6,,87.7,,,,0.002% +T25N,0.8,,22.9,,,,0.00006% +T45I,1.6,1,4.8,,,,0.02% +D48Y,2,1,5.9,,,,0.0005% +M49I,1.1,1,10.1,1.8,,,0.02% +M49L,1.4,1,40.9,1.2,,2,0.0002% +M49T,0.8,1,3.6,1.4,,,0.0005% +M49del,2.8,1,,0.5,,, +L50F,1.5,1,1.1,2.3,1,9,0.04% +G138S,3.5,,,,,1,0.00008% +F140L,5.9,,,,,2,0.0001% +F140S,260,,,,,,0.00004% +N142D,1.5,1,3.2,1.1,,,0.0008% +N142L,2.5,1,,1.4,,,0.0001% +N142S,1.2,1,4.9,1.2,,,0.001% +G143S,147.7,1,15.6,0,,,0.00001% +S144A,7.3,1,17.2,0.6,,2,0.00009% +S144E,81.5,1,,0,,,0.00001% +S144L,1154.1,1,,0,,,0.00006% +S144P,2851.7,1,,0,,,0.00001% +C160F,1,,,0.7,,4,0.01% +M165R,384.2,1,,0,,,0.00001% +M165T,15.8,1,,0.1,,,0.00003% +E166A,10,1,6.3,0.1,1,5,0.00004% +E166G,6.2,1,,0.1,,,0.00004% +E166K,76.6,1,,0,,,0.00003% +E166Q,2.7,1,,1,,,0.00005% +E166V,287.5,1,50.3,0.1,5,3,0.0001% +L167F,7,1,12.8,0.3,,6,0.0002% +P168del,7,1,6.1,,,, +T169I,1.1,1,1.3,,,2,0.003% +H172L,898.8,1,,0,,,0.00001% +H172N,464.1,1,,0,,,0.00001% +H172Q,5.8,1,,0.3,,1,0.00004% +H172Y,24.2,1,,0.2,,2,0.00009% +A173T,2.5,,1.2,,,1,0.002% +A173V,6.4,,1.3,,,3,0.002% +V186A,,1,,,,2,0.0004% +R188G,38,1,,,,2,0.00009% +R188S,2.9,1,5.8,0.9,,,0.0007% +Q189I,4.2,1,,,,,0.00003% +Q189K,8.6,1,1.9,0.3,,,0.0003% +T190I,1.2,1,2.9,1.3,,,0.02% +A191T,1.2,1,4.6,1.7,,3,0.006% +A191V,1.6,1,3.6,1.9,,7,0.08% +Q192A,18.2,1,,0.2,,,0.0007% +Q192C,16.3,1,,0.1,,,0.00006% +Q192D,10.4,1,,0,,,0.00006% +Q192E,34.2,1,,0.1,,,0.00003% +Q192F,24.6,1,,0.3,,,0.00001% +Q192G,34.4,1,,0.1,,,0.00009% +Q192H,24.7,1,,0.1,,,0.00009% +Q192I,13.7,1,,0.2,,,0.00008% +Q192K,27.9,1,,0,,,0.0003% +Q192L,29,1,,0.2,,,0.0002% +Q192N,2.8,1,,0.1,,,0.00001% +Q192P,7.8,1,,0.1,,,0.0002% +Q192R,19.2,1,3.2,0.1,,1,0.0002% +Q192S,24.3,1,,0.1,,,0.0003% +Q192T,16.8,1,,0.1,,,0.0002% +Q192V,10.1,1,,0.1,,,0.0002% +Q192W,12.9,1,,0.1,,,0.00001% +Q192Y,384.2,1,,0,,,0.0001% +A193P,,,,,,2,0.0004% +A194S,3.3,,,,,1,0.002% +P252L,5.8,,1.3,,,5,0.004% +V297A,3,,,,,,0.00001% +S301P,,,,,,2,0.0002% +T304I,2.5,,1,,,8,0.007% +F305L,,,,,,2,0.001% \ No newline at end of file diff --git a/data/RdRP_inhibitors_datasheet.csv b/data/RdRP_inhibitors_datasheet.csv new file mode 100644 index 0000000..22ccd61 --- /dev/null +++ b/data/RdRP_inhibitors_datasheet.csv @@ -0,0 +1,20 @@ +Mutation,RDV: fold,in patient,in vitro,Prevalence +V166A,,,1,0.0008% +V166L,1.5,2,1,0.004% +N198S,,,1,0.01% +R285C,,2,,0.006% +A376V,12.6,,,0.00002% +A449V,,2,,0.07% +F480L,2,,1,0.0002% +D484Y,3.1,1,,0.0008% +A526V,,,1,0.04% +V557L,2,,1,0.0003% +G671S,1,2,,47% +S759A,2.6,,1,0% +V792I,2.6,3,1,0.002% +E796G,2.8,,1,0.0007% +C799F,3.4,,2,0.0009% +C799R,2.8,,1,0.00006% +E802A,3,,,0.0002% +E802D,6,1,1,0.002% +M924R,,2,,0.003% \ No newline at end of file diff --git a/data/spike_mAbs_datasheet.csv b/data/spike_mAbs_datasheet.csv new file mode 100644 index 0000000..dc0631d --- /dev/null +++ b/data/spike_mAbs_datasheet.csv @@ -0,0 +1,165 @@ +Mutation,BAM: fold,BAM: dms,ETE: fold,ETE: dms,CAS: fold,CAS: dms,IMD: fold,IMD: dms,CIL: fold,CIL: dms,TIX: fold,TIX: dms,SOT: fold,SOT: dms,BEB: fold,BEB: dms,REG: fold,AMU: fold,ROM: fold,ADI: fold,C135: fold,C135: dms,C144: fold,C144: dms,in patient,in vitro,Prevalence +P337H,,,,,,,,,,,,,6.3,1,,,,,,,,,,,6,,0.0008% +P337L,,,,,,,5,,,,,,186,0.997,,,,,,,,,,,10,1,0.003% +P337R,,,,,,,,,,,,,234,0.999,,,,,,,,,,,4,,0.0004% +P337S,,,,,,,,,,,,,1.8,0.999,,,,,,,,,,,22,,0.003% +P337T,,,,,,,,,,,,,8,0.992,,,,,,,,,,,,,0.001% +E340A,,,,,,,,,,,,,100,0.997,,,,,,,,,,,13,3,0.002% +E340D,,,,,,,,,,,,,14.8,0.988,,,,,,,,,,,23,,0.003% +E340G,,,,,,,,,,,,,22.6,0.986,,,,,,,,,,,1,,0.0008% +E340K,,,0.4,,,,,,,,,,5148,0.997,,,,,,,,,,,25,1,0.004% +E340Q,,,,,,,,,,,,,50,0.966,,,,,,,,,,,4,,0.0005% +E340V,,,,,,,,,,,,,200,,,,,,,,,,,,7,,0.001% +T345P,,,,,,,,,,,,,,0.991,,,,,,,,,,,,1,0.0001% +R346G,,,,,,,,,,0.606,,,0.9,,,,,,,,,0.697,,,,4,0.005% +R346I,,,,,,,,,200,0.728,,,1.7,,,,,,,,,0.734,,,2,3,0.01% +R346K,1.3,,2.4,,1.1,,2.9,,2.6,,2.2,,1.4,,0.7,,0.3,0.8,21,0.8,2296.4,0.82,0.8,,2,2,8.9% +R346S,,,1.8,,,,,,,0.763,,,1.8,,0.4,,,,,,2700,0.813,0.9,,2,1,0.03% +R346T,,,,,,,,,,0.9,,,1.3,,,,,,,,,0.804,,,3,,0.006% +K356Q,,,,,,,,,,,,,0.9,0.106,,,,,,,,,,,,,0.001% +K356T,,,,,,,,,,,,,5.9,0.9,,,,,,,,,,,3,,0.0008% +S371F,0.9,,143,,13.5,,87,,1.3,,26,,13.3,,2.4,,20.8,124.5,22,51.6,,,,,6,,6.0% +S371L,1.6,,17.1,,3.9,,18.4,,1,,4,,9.7,,2.2,,1.1,17.1,17,15,,,,,2,,17% +D405E,,,,0.186,,,,,,,,,,,,,,,,,,,,,,,0.0006% +D405N,1.8,,20.9,0.275,10.9,,1.9,,1,,2.1,,0.7,,0.5,,1,2,1.6,2.1,,,,,1,1,6.0% +E406D,,,,,51,,,,,,,,,,,,,,,,,,,,,,0.001% +K417E,,,,0.99,165.4,0.564,0.9,,0.4,,,,0.9,,1.3,,,,,,,,,,,9,0.0004% +K417H,,,,0.987,,0.34,,,,,,,,,,,,,,,,,,,,,0.0002% +K417I,,,,0.999,,,,,,,,,,,,,,,,,,,,,1,,0.0002% +K417M,,,,0.966,,,,,,,,,,,,,,,,,,,,,,,0.0003% +K417N,0.6,,381.7,0.997,12.5,,0.7,,0.7,,0.4,,0.7,,0.7,,0.6,1.7,0.5,0.9,0.3,,0.8,,3,6,23% +K417R,,,,0.115,61,,,,,,,,,,,,,,,,,,,,,2,0.004% +K417S,,,,0.98,,,,,,,,,,,,,,,,,,,,,,,0.0002% +K417T,,,49,0.987,7.1,,1.1,,,,,,0.7,,0.7,,0.7,0.9,0.6,,,,,,3,1,1.4% +D420A,,,,0.987,,,,,,,,,,,,,,,,,,,,,,,0.0002% +D420N,2.6,,60.6,0.993,,,,,,,,,,,0.7,,,,,,,,,,,1,0.0008% +N439K,2.4,,0.4,,0.8,,28.8,,1.2,,,,1,,0.5,,,1,0.9,,238.6,,0.8,,,2,0.6% +N440D,,,,,,,,0.473,,,,,1.3,,1.9,,,,,,,,,,1,2,0.0004% +N440E,,,,,,,,0.632,,,,,0.8,,,,,,,,,,,,,,0.0001% +N440I,,,,,,,,0.918,,,,,1.2,,,,,,,,,0.424,,,,,0.0006% +N440K,1.2,,1.1,,1,,92,0.565,1.3,,1,,1.5,,0.8,,0.3,2.1,1.6,1.8,647.1,0.815,1.1,,3,2,23% +N440R,,,,,,,,,,,,,,,,,,,,,,0.23,,,,,0.0002% +N440T,,,,,,,,0.496,,,,,0.8,,,,,,,,,,,,,,0.001% +N440Y,,,,,,,,0.504,,,,,0.7,,,,,,,,,,,,,,0.004% +S443Y,,,,,,,,0.821,,0.194,,,,,,,,,,,,0.585,,,,,0.0001% +K444E,,,,,,,,0.962,291.2,0.946,,,,,,0.81,,,,,,0.627,,,,8,0.0002% +K444F,,,,,,,,0.958,,0.883,,,,,,0.678,,,,,,0.672,,,,,0.0001% +K444I,,,,,,,,0.994,,0.982,,,,,,0.678,,,,,,0.641,,,,,0.0002% +K444L,,,,,,,153,0.971,,0.988,,,,,,0.69,,,,,,0.624,,,,,0.0001% +K444M,,,,,,,1577,0.969,,0.979,,,,,,,,,,,,0.208,,,,1,0.002% +K444N,,,,,,,755,0.943,,0.933,,,,,1901,0.718,,,,,33.7,,1.1,,,7,0.01% +K444R,,,,,,,,,200,0.964,,,,,,,,,,,1.1,,1.2,,,5,0.01% +K444T,,,,,2,,1026.6,0.982,,0.956,,,,,1814,0.635,,,,,44.3,,2,,5,4,0.002% +V445A,,,,,1.8,,548,0.98,35.5,,,,3.4,,83.3,,,,,,,,,,2,5,0.005% +V445D,,,,,,,,0.957,,0.564,,,,,,0.764,,,,,,,,,,1,0.0001% +V445F,,,,,,,,0.885,,,,,,,369,0.654,,,,,,,,,1,1,0.003% +V445I,,,,,,,,,,,,,,,,,,,,,5.6,,1.5,,,,0.003% +V445L,,,,,,,,0.806,,,,,,,,,,,,,7.2,,1.1,,,,0.0002% +G446A,,,,,,,,0.745,,,,,,,,,,,,,,,,,,,0.001% +G446D,,,,,,,,0.978,,,,,,,69,,,,,,,,,,8,4,0.001% +G446I,,,,,,,,0.953,,,,,,,,,,,,,,0.164,,,,1,0.0001% +G446N,,,,,,,,0.924,,,,,,,,,,,,,,,,,,,0.0008% +G446R,,,,,,,,0.953,,,,,,,7,,,,,,,,,,,1,0.002% +G446S,1.2,,0.9,,1.3,,574,0.824,3.7,,1.2,,1.6,,2,,1.1,0.9,1.3,3.4,3.5,,1.1,,1,,17% +G446T,,,,,,,,0.986,,,,,,,,,,,,,,,,,,,0.0002% +G446V,,,0.8,,0.7,,348.5,0.959,3.6,,,,1.5,,4.3,,,0.9,,,858,0.16,1.3,,4,4,0.1% +G447C,,,,,,,,0.591,,,,,,,,,,,,,,,,,,,0.0002% +G447D,,,,,,,,0.986,,0.935,,,,,,,,,,,,,,,,,0.0002% +G447F,,,,,,,,0.908,,0.863,,,,,,,,,,,,,,,,,0.0001% +G447S,,,,,,,,0.652,,0.171,,,,,,,,,,,,,,,,2,0.0007% +G447V,,,,,,,,0.841,,0.882,,,,,,,,,,,,,,,,,0.001% +N448D,,,,,,,,0.613,,0.859,,,,,,,,,,,,,,,,1,0.0005% +N448K,,,,,,,,0.691,,0.97,,,,,,,,,,,,0.47,,,,1,0.0006% +N448T,,,,,,,,0.126,,0.854,,,,,,,,,,,,,,,,,0.0006% +N448Y,,,,,,,,,,0.841,,,,,,,,,,,,0.672,,,,,0.0001% +Y449D,,,,,,,,,,0.674,,,,,,,,,,,,,,,,,0.0007% +N450D,,,,,1.4,,20.9,,382.3,0.7,,,,,,,,,,,,,,,1,5,0.002% +N450K,,,0.4,,,,,,9.1,,,,0.8,,0.7,,,,,,,,,,,2,0.03% +L452M,1,,0.9,,0.4,,1,,1,,0.5,,2.1,,0.6,,,0.9,8.1,1.3,,,,,,,0.03% +L452Q,3,,1.8,,3.1,,4.2,,2.2,,0.5,,2.7,,0.8,,,1.3,21.3,1.3,,,,,,,0.1% +L452R,1254.5,0.934,1,,1.1,,1.7,,5.7,,0.5,,1.1,,0.6,,35,1.3,116,1.1,,,,,4,6,38% +L452W,,0.179,,,,,,,,,,,,,,,,,,,,,,,,,0.002% +Y453F,1.4,,1.4,,456,0.574,1.3,,0.8,,,,1.1,,0.7,,,1.1,1.3,,,,1.1,,2,3,0.03% +Y453H,,,,,,0.427,,,,,,,,,,,,,,,,,,,,,0.0003% +L455F,,,,,80,0.364,1.1,,0.6,,3.6,,0.6,,,,,,,,1.7,,68.3,0.802,3,2,0.03% +L455M,,,,,,,,,,,,,,0.108,,,,,,,,,,,,,0.0002% +L455S,,,,,,0.545,,,,,,,,,,,,,,,,,,0.941,1,,0.002% +L455W,,,,0.966,,0.578,,,,,,,,,,,,,,,,,,0.973,2,,0.0001% +F456C,,,,0.987,,,,,,,,,,,,,,,,,,,,0.994,,,0.0002% +F456L,,,,,,,,,,,,,,,,,,,,,,,,0.946,2,1,0.005% +F456V,,,,0.969,,,,,,,,,,,,,,,,,0.2,,159.9,0.952,,3,0.0004% +S459P,,,,0.685,,,,,,,,,,,,,,,,,,,,,,,0.0004% +N460D,,,,0.105,,,,,,,,,,,,,,,,,,,,,,,0.0008% +N460H,,,,0.975,,,,,,,,,,,,,,,,,,,,,,1,0.0001% +N460I,,,,0.995,,,,,,,,,,,,,,,,,,,,,,,0.001% +N460K,1.5,,76.3,0.996,1.3,,1.3,,0.8,,1,,1.2,,1.2,,,8.9,2.4,,,,,,2,1,0.002% +N460S,0.5,,155.3,0.957,,,,,,,,,,,0.7,,,,,,,,,,,1,0.01% +N460T,2.3,,75,0.992,,,,,,,,,0.6,,,,,,,,,,,,,1,0.0003% +N460Y,1.7,,100,0.886,,,,,,,,,,,,,,,,,,,,,1,1,0.001% +A475D,,,,0.964,,0.381,,,,,,0.929,,,,,,,,,0.5,,1.2,,,1,0.0001% +A475V,,,16.7,0.611,,,,,0.6,,,,2.3,,,,,70.8,,,1.3,,1.2,,2,,0.03% +G476D,,,,,,0.895,,,,,,0.987,,,,,,,,,,,,,1,7,0.0007% +G476R,,,,0.733,,0.469,,,,,,0.973,,,,,,,,,,,,,,,0.0001% +G476T,,,,0.967,,,,,,,,0.929,,,,,,,,,,,,,,,0.0001% +V483A,39.6,,1,,0.4,,0.6,,,,,,,,0.3,,,1.5,,,1,,1,,8,,0.005% +E484A,678.6,0.911,3.4,,9,,1.5,,1.3,,8.1,,0.9,,1.4,,1.1,7.7,1.9,3.6,1.8,,1743.2,0.973,6,5,23% +E484D,100,0.889,,,,,,,,,7.1,,,,1,,,,,,1,,181.7,0.817,1,5,0.003% +E484G,,,,,,,,,,,,,,,,,,,,,,,137.3,0.937,2,3,0.003% +E484K,1333.3,0.97,2.9,,18.4,,1,,1,,8.1,,0.4,,0.7,,8.7,1.4,2.5,,0.4,,1388,0.966,30,33,2.9% +E484P,,0.993,,,,,,,,,,,,,,,,,,,,,,0.992,,,0.0008% +E484Q,100,0.987,1.3,,19,,1,,,,3,,0.4,,0.7,,,1.5,2.6,,1.4,,1743.2,0.991,11,1,0.2% +E484R,,0.991,,,,,,,,,,,,,,,,,,,,,,0.953,,,0.0005% +E484S,,0.702,,,,,,,,,,,,,,,,,,,,,,0.97,,,0.0008% +E484T,,0.954,,,,,,,,,,,,,,,,,,,,,,0.997,,,0.0007% +E484V,,0.977,,,,0.555,,,,,,,,,,,,,,,,,,0.983,2,2,0.01% +G485D,,0.75,,,4.4,,0.6,,,,,0.673,,,,,,,,,,,,,,3,0.001% +G485R,,0.885,,,,,,,,,,,,,,,,,,,,,0.7,,2,,0.001% +F486D,,,,,,0.983,,,,,,0.996,,,,,,,,,,,,,,,0.0002% +F486I,,0.803,,,,0.825,,,,,,0.149,,,1.7,,,,,,,,,,2,1,0.001% +F486L,,0.86,7.6,,61,0.267,0.8,,,,,,1.4,,,,,3.1,2.1,,,,1.1,,,2,0.002% +F486N,,,,0.163,,0.968,,,,,,0.985,,,,,,,,,,,,,,,0.0001% +F486P,,0.878,,0.316,,0.965,,,,,,0.985,,,,,,,,,,,,0.283,,1,0.0002% +F486S,,,,0.249,509.9,0.97,0.4,,,,600,0.997,,,,,,,,,,,,0.39,3,6,0.003% +F486T,,0.804,,0.157,,0.963,,,,,,0.987,,,,,,,,,,,,,,,0.0002% +F486V,490.3,0.73,11.3,0.142,814.9,0.945,0.9,,0.7,,135.5,0.285,1.1,,1.5,,,12.8,1.6,1.3,,,,,1,9,0.0007% +N487D,,,,0.952,,0.819,,,,,,0.939,,,,,,,,,,,,0.448,,3,0.0002% +N487H,,,,0.949,,,,,,,,0.41,,,,,,,,,,,,,,,0.0002% +N487S,,,,0.994,,,,,,,,0.471,,,,,,,,,,,,,,1,0.0002% +Y489H,,,,0.991,,0.943,,,,,,0.42,1.5,,2,,,,,,,,20.2,0.813,1,,0.001% +Y489W,,,,0.913,,0.738,,,,,,0.955,,,,,,,,,,,,,,,0.0002% +F490G,,0.908,,,,,,,,,,,,,,,,,,,,,,0.968,,,0.0001% +F490I,,,,,,,,,,,,,,,,,,,,,,,,0.981,,,0.0007% +F490L,844.3,,2.1,,2.9,,0.8,,,,,,,,1,,,0.7,,,2.1,,233.4,0.967,4,4,0.02% +F490R,,0.985,,,,,,,,,,,,,,,,,,,,,,0.993,,,0.002% +F490S,333.3,0.858,1.1,,0.8,,1.2,,1.1,,,,0.8,,1.7,,,1.3,134.9,,1.2,,4.1,,7,5,0.2% +F490V,,,,,,,,,,,,,,,,,,,,,,,,0.882,,2,0.003% +F490Y,,0.15,,,,,,,,,,,,,,,,,,,,,,,1,,0.002% +Q493D,,,,,,0.795,,,,,,,,,,,,,,,,,,0.866,,,0.0002% +Q493E,,,,,446,0.72,,,,,,,,,,,,,,,,,,,1,,0.009% +Q493H,13.9,,1,,,0.195,,,,,,,,,,,,,,,,,,,,,0.001% +Q493K,2451.3,0.983,54.2,0.378,301,0.633,1.4,,1.6,,1.9,,1.6,,1,,,,,,1.4,,3349,0.996,8,8,0.002% +Q493L,9.9,,3.8,,,0.179,,,,,,,1.1,,,,,,,,2,,20.5,0.72,1,,0.003% +Q493R,1185,0.965,46.4,0.582,42,0.112,1.5,,1.1,,6.5,,1.2,,1,,949,11.6,2,1.4,1,,917.3,0.981,17,6,23% +Q493V,,0.137,,0.131,,,,,,,,,,,,,,,,,,,,0.861,,,0.0002% +S494P,100,0.881,0.6,,2.1,,1.5,,1.2,,,,2,,0.7,,,0.7,1.6,,0.8,,99.1,0.905,12,3,0.2% +S494R,844.3,0.981,3.1,,1.1,,3,,,0.881,,,,,1.5,,,,,,,,,,,,0.0001% +G496S,1.1,,1.3,,1,,5.5,,1.8,,0.9,,1.1,,1.7,,0.6,2.5,1.2,2.2,,,,,,,17% +Q498H,,,,,,,17,,,,,,,,,,,,,,,,,,,,0.0004% +P499H,,,,,,,,0.334,,,,,,,1606,0.644,,,,,,,,,,2,0.001% +P499R,,,,,,,,0.892,,,,,,,976.7,0.668,,,,,,,,,,2,0.008% +P499S,,,,,,,206,0.813,,,,,,,37.5,,,,,,,,,,,,0.003% +P499T,,,,,,,,0.606,,,,,,,,,,,,,,,,,,,0.0007% +N501T,1.1,,10,,,,,,,,,,,,,,,,,,,,,,9,1,0.08% +N501Y,1.1,,5.3,,1,,1,,1.3,,1.5,,1.6,,0.7,,3.8,1,2.3,3.1,1.4,,1.5,,8,3,45% +G504C,,,,0.791,,,,,,,,,,,,,,,,,,,,,,,0.0001% +G504D,,,,0.237,,,,,,,,,,,,,,,,,,,1.8,,1,6,0.002% +G504I,,,,0.908,,,,,,,,,,,,,,,,,,,,,,,0.0002% +G504L,,,,0.684,,,,,,,,,,,,,,,,,,,,,,,0.0002% +G504N,,,,0.295,,,,,,,,,,,,,,,,,,,,,,,0.0006% +G504R,,,,0.281,,,,,,,,,,,,,,,,,,,,,,,0.0001% +G504V,,,,0.705,,,,,,,,,,,,,,,,,,,,,,,0.0006% +P507A,,,,,,,,0.361,,,,,,,,,,,,,,,,,,,0.0005% +N856K,1.4,,9.2,,2.1,,1.4,,1.3,,2.9,,1.7,,1.3,,1.3,1.6,2.3,1.7,,,,,,,18% +N969K,1.5,,5.7,,1.4,,1.6,,1.4,,2.4,,1.5,,3.9,,0.9,3.1,1.4,1.9,,,,,,,24% +E990A,,,,,,,,,,,6.1,,,,,,,,,,,,,,,,0.0004% +T1009I,,,,,,,,,,,8.2,,,,,,,,,,,,,,,,0.01% \ No newline at end of file From c30f6145f7d3f2520daff1b042a8a907f5482686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 25 Nov 2024 14:16:01 +0100 Subject: [PATCH 19/20] add test --- resistance_mut.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resistance_mut.py b/resistance_mut.py index 0c4d5e9..c0778fd 100644 --- a/resistance_mut.py +++ b/resistance_mut.py @@ -102,7 +102,15 @@ def plot_heatmap(df): def app(): st.title("Resistance Mutations") - st.write("This page allows you to run the Lollipop variant deconvolution tool with custom variant definitions.") + st.write("This page allows you to visualize the numer of observed resistance mutations over time.") + st.write("The data is fetched from the COV-Spectrum API and currently used clinical data.") + + st.write("The sets of resistance mutations are provide from Stanfords Coronavirus Antivirial & Reistance Database. Last updated 05//14/2024") + + st.write("This is a demo frontend to later make the first queries to SILO for wastewater data.") + + # make a horizontal line + st.markdown("---") st.write("Select from the following resistance mutation sets:") From 10106bbf249738786c3015fd275159a644da1f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20J=2E=20K=C3=B6hn?= Date: Mon, 16 Dec 2024 13:15:01 +0100 Subject: [PATCH 20/20] add SILO --- app.py | 4 +- config.yaml | 3 +- resistance_mut_silo.py | 166 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 resistance_mut_silo.py diff --git a/app.py b/app.py index 8d0fb4c..43a2ebe 100644 --- a/app.py +++ b/app.py @@ -3,12 +3,14 @@ import mutation_freq import variant_deconv import resistance_mut +import resistance_mut_silo PAGES = { "Home": {"module": index}, "Mutation Frequency": {"module": mutation_freq}, "Variant Deconvolution": {"module": variant_deconv}, - "Resistance Mutations": {"module": resistance_mut} + "Resistance Mutations (clinical)": {"module": resistance_mut}, + "Resistance Mutations (ww)": {"module": resistance_mut_silo} } def sidebar(): diff --git a/config.yaml b/config.yaml index 50ceba4..b579ffb 100644 --- a/config.yaml +++ b/config.yaml @@ -1,2 +1,3 @@ server: - ip_address: "http://68.221.168.92:8000" \ No newline at end of file + ip_address: "http://68.221.168.92:8000" + lapis_address: "http://localhost:8080" \ No newline at end of file diff --git a/resistance_mut_silo.py b/resistance_mut_silo.py new file mode 100644 index 0000000..aab2bd6 --- /dev/null +++ b/resistance_mut_silo.py @@ -0,0 +1,166 @@ +import json +from matplotlib import pyplot as plt +import numpy as np +import streamlit as st +import requests +import yaml +import pandas as pd +import logging +import aiohttp +import asyncio +import seaborn as sns + + +# Load configuration from config.yaml +with open('config.yaml', 'r') as file: + config = yaml.safe_load(file) + +server_ip = config.get('server', {}).get('lapis_address', 'http://default_ip:8000') + +async def fetch_data(session, mutation, date_range): + payload = { + "aminoAcidMutations": [mutation], + "sampling_dateFrom": date_range[0].strftime('%Y-%m-%d'), + "sampling_dateTo": date_range[1].strftime('%Y-%m-%d'), + "fields": ["sampling_date"] + } + + async with session.post( + f'{server_ip}/sample/aggregated', + headers={ + 'accept': 'application/json', + 'Content-Type': 'application/json' + }, + json=payload + ) as response: + if response.status == 200: + data = await response.json() + return {"mutation": mutation, + "data": data.get('data', [])} + else: + logging.error(f"Failed to fetch data for mutation {mutation}.") + logging.error(f"Status code: {response.status}") + logging.error(await response.text()) + return {"mutation": mutation, + "data": None} + +async def fetch_all_data(mutations, date_range): + async with aiohttp.ClientSession() as session: + tasks = [fetch_data(session, mutation, date_range) for mutation in mutations] + return await asyncio.gather(*tasks) + +def fetch_reformat_data(formatted_mutations, date_range): + all_data = asyncio.run(fetch_all_data(formatted_mutations, date_range)) + + # get dates from date_range + dates = pd.date_range(date_range[0], date_range[1]).strftime('%Y-%m-%d') + + # get all unique dates + # dates = set() + # for data in all_data: + # if data['data']: + # for d in data['data']: + # dates.add(d['date']) + + + # make a dataframe with the dates as columns and the mutations as rows + df = pd.DataFrame(index=formatted_mutations, columns=list(dates)) + + # fill the dataframe with the data + for data in all_data: + if data['data']: + for d in data['data']: + df.at[data['mutation'], d['sampling_date']] = d['count'] + + return df + + +def plot_heatmap(df): + # Replace None with np.nan and remove commas from numbers + df = df.replace({None: np.nan, ',': ''}, regex=True).astype(float) + + # Create a colormap with a custom color for NaN values + cmap = sns.color_palette("Blues", as_cmap=True) + cmap.set_bad(color='#FFCCCC') # Set NaN values to a fainter red color + + # Adjust the plot size based on the number of rows in the dataframe + height = max(8, len(df) * 0.3) # Minimum height of 8, with 0.5 units per row + fig, ax = plt.subplots(figsize=(15, height)) + + annot = True if df.shape[0] * df.shape[1] <= 100 else False # Annotate only if the plot is small enough + sns.heatmap(df, cmap=cmap, ax=ax, cbar_kws={'label': 'Occurrence Frequency', 'orientation': 'horizontal'}, + linewidths=.5, linecolor='lightgrey', annot=annot, fmt=".1f", + annot_kws={"size": 10}, mask=df.isnull(), cbar=True, cbar_ax=fig.add_axes([0.15, 0.90, 0.7, 0.02])) + + # Set axis labels + ax.set_xticks([0, len(df.columns) // 2, len(df.columns) - 1]) + ax.set_xticklabels([df.columns[0], df.columns[len(df.columns) // 2], df.columns[-1]], rotation=45) + return fig + + + +def app(): + st.title("Resistance Mutations from Wastewater Data") + + st.write("This page allows you to visualize the numer of observed resistance mutations over time.") + st.write("The data is fetched from the WISE-CovSpectrum API and currently cointains demo data for Sep-Oct 2024.") + + st.write("The sets of resistance mutations are provide from Stanfords Coronavirus Antivirial & Reistance Database. Last updated 05/14/2024") + + st.write("This is a demo frontend to later make the first queries to SILO for wastewater data.") + + # make a horizontal line + st.markdown("---") + + st.write("Select from the following resistance mutation sets:") + + # TODO: currently hardcoded, should be fetched from the server + options = { + "3CLpro Inhibitors": 'data/3CLpro_inhibitors_datasheet.csv', + "RdRP Inhibitors": 'data/RdRP_inhibitors_datasheet.csv', + "Spike mAbs": 'data/spike_mAbs_datasheet.csv' + } + + selected_option = st.selectbox("Select a resistance mutation set:", options.keys()) + + df = pd.read_csv(options[selected_option]) + + gene_name = { + "3CLpro Inhibitors": "ORF1a", + "RdRP Inhibitors": "ORF1b", + "Spike mAbs": "S" + } + + # get the gene name + gene = gene_name[selected_option] + + # Get the list of mutations for the selected set + mutations = df['Mutation'].tolist() + # Lambda function to format the mutation list, from S24L to S:24L + format_mutation = lambda x: f"{gene}:{x[0]}{x[1:]}" + #format_mutation = lambda x: f"{x[0]}:{x[1:]}" + # Apply the lambda function to each element in the mutations list + formatted_mutations = [format_mutation(mutation) for mutation in mutations] + + st.write(f"Selected mutations:") + st.write(formatted_mutations) + + + # Allow the user to choose a date range + st.write("Select a date range:") + date_range = st.date_input("Select a date range:", [pd.to_datetime("2024-09-30"), pd.to_datetime("2024-10-16")]) + + if st.button("Fetch Data"): + st.write("Fetching data...") + df = fetch_reformat_data(formatted_mutations, date_range) + + # Check if the dataframe is all NaN + if df.isnull().all().all(): + st.error("The fetched data contains only NaN values. Please try a different date range or mutation set.") + else: + # Plot the heatmap + fig = plot_heatmap(df) + st.pyplot(fig) + +if __name__ == "__main__": + app() \ No newline at end of file