import streamlit as st
import pandas as pd
import numpy as np
import json
import base64
import io
from datetime import datetime
import os
# Page configuration
st.set_page_config(
page_title="Collaborative Features",
page_icon="👥",
layout="wide"
)
# Page title
st.title("👥 Collaborative Features")
st.markdown("""
Share your analysis, save projects for later, and collaborate with others
by importing and exporting complete analysis workflows and results.
""")
# Create tabs for different collaborative features
tab1, tab2, tab3 = st.tabs([
"Project Management",
"Share Analysis",
"Import/Export Workflows"
])
# Initialize project state if not exists
if 'projects' not in st.session_state:
st.session_state.projects = []
# Project Management Tab
with tab1:
st.subheader("Project Management")
# Project saving section
st.markdown("""
Save your current dataset and analysis settings as a project to continue working on it later.
Projects are stored in your browser and will be available when you return.
""")
# Form for saving a project
with st.form("save_project_form"):
project_name = st.text_input("Project Name", placeholder="My Data Analysis Project")
project_description = st.text_area("Description (optional)", placeholder="Brief description of this analysis project")
col1, col2, col3 = st.columns([2, 1, 1])
with col1:
project_tags = st.text_input("Tags (comma separated)", placeholder="finance, stocks, analysis")
with col2:
save_data = st.checkbox("Include Dataset", value=True)
with col3:
overwrite = st.checkbox("Overwrite if exists", value=False)
save_button = st.form_submit_button("Save Project")
if save_button:
if not project_name:
st.error("Please provide a project name.")
else:
try:
# Check if project exists
existing_project = False
for i, project in enumerate(st.session_state.projects):
if project['name'] == project_name:
existing_project = True
if overwrite:
# Update existing project
project_index = i
break
else:
st.error(f"Project '{project_name}' already exists. Use 'Overwrite if exists' to replace it.")
break
# If project doesn't exist or we're overwriting
if not existing_project or (existing_project and overwrite):
# Create project data structure
project_data = {
'name': project_name,
'description': project_description,
'tags': [tag.strip() for tag in project_tags.split(',')] if project_tags else [],
'date_created': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'dataset_name': st.session_state.get('dataset_name', 'Unknown'),
'settings': {}
}
# Include dataset if selected
if save_data and 'df' in st.session_state and st.session_state.df is not None:
# Convert dataframe to CSV string
csv_buffer = io.StringIO()
st.session_state.df.to_csv(csv_buffer, index=False)
csv_str = csv_buffer.getvalue()
# Base64 encode to reduce storage size
csv_bytes = csv_str.encode()
b64_csv = base64.b64encode(csv_bytes).decode()
# Add to project data
project_data['dataset'] = b64_csv
project_data['row_count'] = len(st.session_state.df)
project_data['column_count'] = len(st.session_state.df.columns)
else:
project_data['dataset'] = None
# Save the project
if existing_project and overwrite:
st.session_state.projects[project_index] = project_data
st.success(f"Project '{project_name}' updated successfully!")
else:
st.session_state.projects.append(project_data)
st.success(f"Project '{project_name}' saved successfully!")
except Exception as e:
st.error(f"Error saving project: {e}")
# Project list section
st.subheader("Your Saved Projects")
if not st.session_state.projects:
st.info("You don't have any saved projects yet.")
else:
# Filter and search options
col1, col2 = st.columns([3, 1])
with col1:
search_term = st.text_input("Search Projects", placeholder="Enter project name or tag")
with col2:
sort_by = st.selectbox("Sort By", ["Date (newest)", "Date (oldest)", "Name (A-Z)", "Name (Z-A)"])
# Filter projects based on search term
filtered_projects = st.session_state.projects
if search_term:
filtered_projects = [
p for p in filtered_projects if (
search_term.lower() in p['name'].lower() or
search_term.lower() in p['description'].lower() or
any(search_term.lower() in tag.lower() for tag in p['tags'])
)
]
# Sort projects
if sort_by == "Date (newest)":
filtered_projects = sorted(filtered_projects, key=lambda p: p['date_created'], reverse=True)
elif sort_by == "Date (oldest)":
filtered_projects = sorted(filtered_projects, key=lambda p: p['date_created'])
elif sort_by == "Name (A-Z)":
filtered_projects = sorted(filtered_projects, key=lambda p: p['name'])
elif sort_by == "Name (Z-A)":
filtered_projects = sorted(filtered_projects, key=lambda p: p['name'], reverse=True)
# Display projects in a grid
if filtered_projects:
# Create 3 columns for the grid
cols = st.columns(3)
# Display each project in the grid
for i, project in enumerate(filtered_projects):
col_index = i % 3
with cols[col_index]:
st.markdown(f"""
{project['name']}
{project['date_created']}
{project['description']}
{'Dataset: ' + (str(project.get('row_count', 'N/A')) + ' rows × ' + str(project.get('column_count', 'N/A')) + ' columns' if project.get('dataset') else 'No dataset')}
{', '.join(['#' + tag for tag in project['tags']]) if project['tags'] else 'No tags'}
""", unsafe_allow_html=True)
c1, c2, c3 = st.columns([1, 1, 1])
with c1:
if st.button("Load", key=f"load_{i}"):
# Load the project
if project.get('dataset'):
try:
# Decode base64 to CSV
b64_csv = project['dataset']
csv_bytes = base64.b64decode(b64_csv)
csv_str = csv_bytes.decode()
# Load CSV into dataframe
csv_buffer = io.StringIO(csv_str)
loaded_df = pd.read_csv(csv_buffer)
# Update session state
st.session_state.df = loaded_df
st.session_state.dataset_name = project['name']
st.success(f"Project '{project['name']}' loaded successfully!")
st.rerun()
except Exception as e:
st.error(f"Error loading project data: {e}")
else:
st.warning("This project does not include dataset. Only project metadata was loaded.")
with c2:
if st.button("Delete", key=f"delete_{i}"):
# Ask for confirmation
st.session_state.delete_index = i
st.info(f"Are you sure you want to delete project '{project['name']}'?")
st.button("Yes, Delete", key=f"confirm_delete_{i}", on_click=lambda: delete_project(i))
with c3:
if st.button("Export", key=f"export_{i}"):
# Export the project to JSON
try:
project_json = json.dumps(project, indent=2)
b64 = base64.b64encode(project_json.encode()).decode()
href = f'Download Project JSON'
st.markdown(href, unsafe_allow_html=True)
except Exception as e:
st.error(f"Error exporting project: {e}")
else:
st.info("No projects match your search criteria.")
# Function to delete a project
def delete_project(index):
if 0 <= index < len(st.session_state.projects):
project_name = st.session_state.projects[index]['name']
del st.session_state.projects[index]
st.success(f"Project '{project_name}' deleted successfully!")
st.rerun()
# Share Analysis Tab
with tab2:
st.subheader("Share Your Analysis")
# Analysis report generation
st.markdown("""
Generate a sharable report of your current analysis, including visualizations and insights.
You can download the report in various formats or get a shareable link.
""")
# Check if data is loaded
if 'df' not in st.session_state or st.session_state.df is None:
st.warning("Please load a dataset first to generate a report.")
else:
# Report options
st.subheader("Report Configuration")
col1, col2 = st.columns(2)
with col1:
report_title = st.text_input("Report Title", value=f"Analysis of {st.session_state.dataset_name}")
report_author = st.text_input("Author/Organization")
with col2:
include_options = st.multiselect(
"Include in Report",
["Dataset Info", "Data Preview", "Summary Statistics", "Visualizations", "Insights"],
default=["Dataset Info", "Data Preview", "Summary Statistics"]
)
report_format = st.selectbox(
"Report Format",
["HTML", "PDF", "JSON"]
)
if st.button("Generate Report"):
with st.spinner("Generating report..."):
try:
# Create basic report data
report_data = {
"title": report_title,
"author": report_author,
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"dataset_name": st.session_state.dataset_name,
"sections": []
}
# Include selected sections
if "Dataset Info" in include_options:
dataset_info = {
"section_title": "Dataset Information",
"content": {
"rows": len(st.session_state.df),
"columns": len(st.session_state.df.columns),
"column_names": st.session_state.df.columns.tolist(),
"data_types": {col: str(dtype) for col, dtype in st.session_state.df.dtypes.items()}
}
}
report_data["sections"].append(dataset_info)
if "Data Preview" in include_options:
data_preview = {
"section_title": "Data Preview",
"content": {
"preview": st.session_state.df.head(10).to_dict(orient="records")
}
}
report_data["sections"].append(data_preview)
if "Summary Statistics" in include_options:
# Get numeric columns for summary stats
numeric_cols = st.session_state.df.select_dtypes(include=['number']).columns.tolist()
if numeric_cols:
summary_stats = {
"section_title": "Summary Statistics",
"content": {
"stats": st.session_state.df[numeric_cols].describe().to_dict()
}
}
report_data["sections"].append(summary_stats)
# Generate report based on format
if report_format == "JSON":
# Convert report data to JSON
report_json = json.dumps(report_data, indent=2)
# Create download link
b64 = base64.b64encode(report_json.encode()).decode()
filename = f"{report_title.replace(' ', '_')}_report.json"
href = f'Download Report JSON'
st.success("Report generated successfully!")
st.markdown(href, unsafe_allow_html=True)
elif report_format == "HTML":
# Create a simple HTML report
html_report = f"""
{report_title}
{report_title}
"""
# Add sections
for section in report_data["sections"]:
section_title = section["section_title"]
content = section["content"]
html_report += f"{section_title}
"
if section_title == "Dataset Information":
html_report += f"""
Rows: {content['rows']}
Columns: {content['columns']}
Columns:
| Column Name |
Data Type |
"""
for col in content['column_names']:
html_report += f"""
| {col} |
{content['data_types'][col]} |
"""
html_report += "
"
elif section_title == "Data Preview":
preview = content['preview']
if preview:
# Create table headers
html_report += """
"""
for col in preview[0].keys():
html_report += f"| {col} | "
html_report += "
"
# Add data rows
for row in preview:
html_report += ""
for col, val in row.items():
html_report += f"| {val} | "
html_report += "
"
html_report += "
"
elif section_title == "Summary Statistics":
stats = content['stats']
if stats:
# Create table headers
html_report += """
| Statistic |
"""
for col in stats.keys():
html_report += f"{col} | "
html_report += "
"
# Add stats rows
for stat in ['count', 'mean', 'std', 'min', '25%', '50%', '75%', 'max']:
html_report += f"| {stat} | "
for col, values in stats.items():
value = values.get(stat, "")
if isinstance(value, (int, float)):
value = f"{value:.4f}" if stat != 'count' else f"{int(value)}"
html_report += f"{value} | "
html_report += "
"
html_report += "
"
# Close HTML
html_report += """
"""
# Create download link
b64 = base64.b64encode(html_report.encode()).decode()
filename = f"{report_title.replace(' ', '_')}_report.html"
href = f'Download HTML Report'
st.success("Report generated successfully!")
st.markdown(href, unsafe_allow_html=True)
# Preview
with st.expander("Preview HTML Report", expanded=False):
st.components.v1.html(html_report, height=500, scrolling=True)
elif report_format == "PDF":
st.info("PDF export requires additional libraries. Please use HTML or JSON format instead.")
except Exception as e:
st.error(f"Error generating report: {e}")
# Import/Export Workflows Tab
with tab3:
st.subheader("Import and Export Workflows")
st.markdown("""
Save your entire analysis workflow, including dataset, transformations, and visualizations.
Export workflows to share with others or import workflows created by your colleagues.
""")
# Export workflow section
st.subheader("Export Current Workflow")
# Check if data is loaded
if 'df' not in st.session_state or st.session_state.df is None:
st.warning("Please load a dataset first to export a workflow.")
else:
# Workflow export options
include_data = st.checkbox("Include Dataset", value=True)
if st.button("Export Workflow"):
with st.spinner("Preparing workflow export..."):
try:
# Create workflow data structure
workflow_data = {
'name': st.session_state.get('dataset_name', 'Unnamed Workflow'),
'date_exported': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'settings': {}
}
# Include dataset if selected
if include_data:
# Convert dataframe to CSV string
csv_buffer = io.StringIO()
st.session_state.df.to_csv(csv_buffer, index=False)
csv_str = csv_buffer.getvalue()
# Base64 encode to reduce storage size
csv_bytes = csv_str.encode()
b64_csv = base64.b64encode(csv_bytes).decode()
# Add to workflow data
workflow_data['dataset'] = b64_csv
workflow_data['row_count'] = len(st.session_state.df)
workflow_data['column_count'] = len(st.session_state.df.columns)
else:
workflow_data['dataset'] = None
# Convert workflow to JSON
workflow_json = json.dumps(workflow_data, indent=2)
# Create download link
b64 = base64.b64encode(workflow_json.encode()).decode()
filename = f"{workflow_data['name'].replace(' ', '_')}_workflow.json"
href = f'Download Workflow'
st.success("Workflow exported successfully!")
st.markdown(href, unsafe_allow_html=True)
except Exception as e:
st.error(f"Error exporting workflow: {e}")
# Import workflow section
st.subheader("Import Workflow")
uploaded_file = st.file_uploader("Upload Workflow JSON", type=['json'])
if uploaded_file is not None:
try:
# Load JSON from uploaded file
workflow_json = uploaded_file.read()
workflow_data = json.loads(workflow_json)
# Display workflow info
st.write(f"**Workflow Name:** {workflow_data.get('name', 'Unnamed Workflow')}")
st.write(f"**Date Exported:** {workflow_data.get('date_exported', 'Unknown')}")
if workflow_data.get('dataset') is not None:
st.write(f"**Includes Dataset:** Yes ({workflow_data.get('row_count', 'N/A')} rows × {workflow_data.get('column_count', 'N/A')} columns)")
else:
st.write("**Includes Dataset:** No")
# Import button
if st.button("Import Workflow"):
with st.spinner("Importing workflow..."):
if workflow_data.get('dataset') is not None:
try:
# Decode base64 to CSV
b64_csv = workflow_data['dataset']
csv_bytes = base64.b64decode(b64_csv)
csv_str = csv_bytes.decode()
# Load CSV into dataframe
csv_buffer = io.StringIO(csv_str)
loaded_df = pd.read_csv(csv_buffer)
# Update session state
st.session_state.df = loaded_df
st.session_state.dataset_name = workflow_data['name']
st.success(f"Workflow '{workflow_data['name']}' imported successfully with dataset!")
st.rerun()
except Exception as e:
st.error(f"Error importing workflow dataset: {e}")
else:
st.warning("This workflow does not include a dataset. Only workflow metadata was imported.")
except Exception as e:
st.error(f"Error parsing workflow file: {e}")
# Sidebar with educational content
with st.sidebar:
st.title("📚 Collaborative Data Science")
st.markdown("""
### Benefits of Collaboration in Data Science
Collaborative data science leads to:
- **Diverse perspectives** that enhance analysis
- **Knowledge sharing** across team members
- **Faster problem solving** through combined expertise
- **Increased reproducibility** with shared workflows
- **Better documentation** for future reference
### Best Practices for Collaboration
1. **Document your analysis**
- Include clear descriptions of what you did and why
- Note any assumptions made
- Explain preprocessing steps
2. **Use version control**
- Save your work regularly
- Track changes over time
- Create branches for experimental analyses
3. **Standardize workflows**
- Use consistent naming conventions
- Organize projects with clear structure
- Define standard preprocessing steps
4. **Share meaningful insights**
- Focus on key findings rather than all results
- Use visualizations effectively
- Relate analysis to business questions
5. **Request specific feedback**
- Ask targeted questions when sharing work
- Be open to constructive criticism
- Incorporate feedback into iterations
### Data Privacy Considerations
- Always consider data sensitivity when sharing
- Remove or anonymize personally identifiable information
- Follow relevant regulations (GDPR, HIPAA, etc.)
- Use secure methods for sharing sensitive data
""")