Project Final Report#
Due Date: See course schedule
Purpose#
The final report is your complete analysis notebook demonstrating mastery of machine learning applied to a chemical engineering problem. This is your opportunity to showcase everything you’ve learned in the course.
! curl -LsSf https://astral.sh/uv/install.sh | sh && \
uv pip install -q --system "s26-06642 @ git+https://github.com/jkitchin/s26-06642.git"
from pycse.colab import pdf
downloading uv 0.10.3 x86_64-unknown-linux-gnu
no checksums to verify
installing to /home/runner/.local/bin
uv
uvx
everything's installed!
Report Structure#
Your report should follow this structure (approximate page lengths for guidance):
Introduction (1-2 pages)
Data (1-2 pages)
Methods (2-3 pages)
Results (2-3 pages)
Discussion (1-2 pages)
Conclusions (0.5 page)
1. Introduction#
Include:
Problem motivation: Why does this problem matter?
Background and prior work: What has been done before?
Objectives: What specific questions are you answering?
Write your introduction here
2. Data#
Include:
Data source and collection method
Feature descriptions (what each column means)
Exploratory data analysis with visualizations
Preprocessing steps (scaling, encoding, missing values, etc.)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
# Load your data
# df = pd.read_csv('your_data.csv')
# Data overview
# df.head()
# Summary statistics
# df.describe()
# EDA visualizations
# Add distribution plots, correlation heatmaps, scatter plots, etc.
Describe your data and preprocessing steps
3. Methods#
Include:
Model selection rationale: Why did you choose these methods?
Hyperparameter tuning approach
Validation strategy (train/test split, cross-validation)
# Prepare data for modeling
# X = df[feature_columns]
# y = df[target_column]
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Model training
# Try multiple models and compare
# Hyperparameter tuning
# from sklearn.model_selection import GridSearchCV
Explain your methodology choices
4. Results#
Include:
Model performance metrics (with appropriate metrics for your problem)
Comparison of different methods
Feature importance or model interpretability analysis
Uncertainty quantification (if applicable)
# Performance metrics
# y_pred = model.predict(X_test)
# print(f"R² Score: {r2_score(y_test, y_pred):.4f}")
# print(f"RMSE: {np.sqrt(mean_squared_error(y_test, y_pred)):.4f}")
# print(f"MAE: {mean_absolute_error(y_test, y_pred):.4f}")
# Predictions vs actual plot
# plt.figure(figsize=(8, 6))
# plt.scatter(y_test, y_pred, alpha=0.5)
# plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2)
# plt.xlabel('Actual')
# plt.ylabel('Predicted')
# plt.title('Predictions vs Actual')
# plt.tight_layout()
# Model comparison table
# Create a DataFrame comparing different models
# Feature importance
# import shap
# or use model.feature_importances_ for tree-based models
Present and interpret your results
5. Discussion#
Include:
Key findings: What did you learn?
Physical/chemical interpretation: Do the results make sense?
Limitations: What are the weaknesses of your approach?
Future work: What would you do differently or next?
Write your discussion here
6. Conclusions#
Summarize your main contributions and findings in a few sentences.
Write your conclusions here
What Success Looks Like#
A successful final report will demonstrate:
Criterion |
Expectation |
|---|---|
Problem Formulation |
Clear, relevant, well-motivated problem |
Data Analysis |
Thorough EDA with informative visualizations |
Methodology |
Appropriate methods with proper validation |
Results |
Clear presentation with proper metrics |
Interpretation |
Domain insights and meaningful conclusions |
Communication |
Clear writing, well-organized, good visualizations |
Code Quality |
Clean, documented, reproducible code |
Excellent Reports Will Also Have#
Multiple models compared fairly
Thoughtful hyperparameter tuning
Feature importance or SHAP analysis
Uncertainty quantification
Physical interpretation of results
Honest discussion of limitations
Red Flags (things to avoid)#
Code that doesn’t run
No train/test split (data leakage)
Only accuracy reported for imbalanced classification
No visualizations
Results that don’t make physical sense (and aren’t discussed)
Copy-pasted code without understanding
Missing sections
Submission#
Run the cell below to generate a PDF for submission.
pdf("project-report.pdf")
---------------------------------------------------------------------------
ConnectionRefusedError Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/connection.py:204, in HTTPConnection._new_conn(self)
203 try:
--> 204 sock = connection.create_connection(
205 (self._dns_host, self.port),
206 self.timeout,
207 source_address=self.source_address,
208 socket_options=self.socket_options,
209 )
210 except socket.gaierror as e:
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/util/connection.py:85, in create_connection(address, timeout, source_address, socket_options)
84 try:
---> 85 raise err
86 finally:
87 # Break explicitly a reference cycle
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/util/connection.py:73, in create_connection(address, timeout, source_address, socket_options)
72 sock.bind(source_address)
---> 73 sock.connect(sa)
74 # Break explicitly a reference cycle
ConnectionRefusedError: [Errno 111] Connection refused
The above exception was the direct cause of the following exception:
NewConnectionError Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/connectionpool.py:787, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
786 # Make the request on the HTTPConnection object
--> 787 response = self._make_request(
788 conn,
789 method,
790 url,
791 timeout=timeout_obj,
792 body=body,
793 headers=headers,
794 chunked=chunked,
795 retries=retries,
796 response_conn=response_conn,
797 preload_content=preload_content,
798 decode_content=decode_content,
799 **response_kw,
800 )
802 # Everything went great!
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/connectionpool.py:493, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
492 try:
--> 493 conn.request(
494 method,
495 url,
496 body=body,
497 headers=headers,
498 chunked=chunked,
499 preload_content=preload_content,
500 decode_content=decode_content,
501 enforce_content_length=enforce_content_length,
502 )
504 # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
505 # legitimately able to close the connection after sending a valid response.
506 # With this behaviour, the received response is still readable.
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/connection.py:500, in HTTPConnection.request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
499 self.putheader(header, value)
--> 500 self.endheaders()
502 # If we're given a body we start sending that in chunks.
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/http/client.py:1298, in HTTPConnection.endheaders(self, message_body, encode_chunked)
1297 raise CannotSendHeader()
-> 1298 self._send_output(message_body, encode_chunked=encode_chunked)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/http/client.py:1058, in HTTPConnection._send_output(self, message_body, encode_chunked)
1057 del self._buffer[:]
-> 1058 self.send(msg)
1060 if message_body is not None:
1061
1062 # create a consistent interface to message_body
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/http/client.py:996, in HTTPConnection.send(self, data)
995 if self.auto_open:
--> 996 self.connect()
997 else:
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/connection.py:331, in HTTPConnection.connect(self)
330 def connect(self) -> None:
--> 331 self.sock = self._new_conn()
332 if self._tunnel_host:
333 # If we're tunneling it means we're connected to our proxy.
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/connection.py:219, in HTTPConnection._new_conn(self)
218 except OSError as e:
--> 219 raise NewConnectionError(
220 self, f"Failed to establish a new connection: {e}"
221 ) from e
223 sys.audit("http.client.connect", self, self.host, self.port)
NewConnectionError: HTTPConnection(host='10.1.0.65', port=9000): Failed to establish a new connection: [Errno 111] Connection refused
The above exception was the direct cause of the following exception:
MaxRetryError Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/requests/adapters.py:644, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
643 try:
--> 644 resp = conn.urlopen(
645 method=request.method,
646 url=url,
647 body=request.body,
648 headers=request.headers,
649 redirect=False,
650 assert_same_host=False,
651 preload_content=False,
652 decode_content=False,
653 retries=self.max_retries,
654 timeout=timeout,
655 chunked=chunked,
656 )
658 except (ProtocolError, OSError) as err:
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/connectionpool.py:841, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
839 new_e = ProtocolError("Connection aborted.", new_e)
--> 841 retries = retries.increment(
842 method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
843 )
844 retries.sleep()
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/urllib3/util/retry.py:535, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
534 reason = error or ResponseError(cause)
--> 535 raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type]
537 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)
MaxRetryError: HTTPConnectionPool(host='10.1.0.65', port=9000): Max retries exceeded with url: /api/sessions (Caused by NewConnectionError("HTTPConnection(host='10.1.0.65', port=9000): Failed to establish a new connection: [Errno 111] Connection refused"))
During handling of the above exception, another exception occurred:
ConnectionError Traceback (most recent call last)
Cell In[13], line 1
----> 1 pdf("project-report.pdf")
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/pycse/colab.py:287, in pdf(line)
285 delay = 10000
286 plotly = "-p" in args
--> 287 pdf_from_html(pdf, verbose, plotly, delay)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/pycse/colab.py:122, in pdf_from_html(pdf, verbose, plotly, javascript_delay)
119 if verbose:
120 print("PDF via wkhtmltopdf")
--> 122 fname, fid = current_notebook()
123 ipynb = notebook_string(fid)
125 if plotly:
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/pycse/colab.py:86, in current_notebook()
84 ip = gethostbyname(gethostname())
85 url = f"http://{ip}:9000/api/sessions"
---> 86 d = requests.get(url).json()[0]
87 fid = d["path"].split("=")[1]
88 fname = d["name"]
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/requests/api.py:73, in get(url, params, **kwargs)
62 def get(url, params=None, **kwargs):
63 r"""Sends a GET request.
64
65 :param url: URL for the new :class:`Request` object.
(...) 70 :rtype: requests.Response
71 """
---> 73 return request("get", url, params=params, **kwargs)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/requests/api.py:59, in request(method, url, **kwargs)
55 # By using the 'with' statement we are sure the session is closed, thus we
56 # avoid leaving sockets open which can trigger a ResourceWarning in some
57 # cases, and look like a memory leak in others.
58 with sessions.Session() as session:
---> 59 return session.request(method=method, url=url, **kwargs)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
584 send_kwargs = {
585 "timeout": timeout,
586 "allow_redirects": allow_redirects,
587 }
588 send_kwargs.update(settings)
--> 589 resp = self.send(prep, **send_kwargs)
591 return resp
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/requests/sessions.py:703, in Session.send(self, request, **kwargs)
700 start = preferred_clock()
702 # Send the request
--> 703 r = adapter.send(request, **kwargs)
705 # Total elapsed time of the request (approximately)
706 elapsed = preferred_clock() - start
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/requests/adapters.py:677, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
673 if isinstance(e.reason, _SSLError):
674 # This branch is for urllib3 v1.22 and later.
675 raise SSLError(e, request=request)
--> 677 raise ConnectionError(e, request=request)
679 except ClosedPoolError as e:
680 raise ConnectionError(e, request=request)
ConnectionError: HTTPConnectionPool(host='10.1.0.65', port=9000): Max retries exceeded with url: /api/sessions (Caused by NewConnectionError("HTTPConnection(host='10.1.0.65', port=9000): Failed to establish a new connection: [Errno 111] Connection refused"))