OpenEDG PCAD study materials
OpenEDG PCAD study resources

OpenEDG PCAD common mistakes

OpenEDG PCAD · Understand the traps and how to avoid them.

Mistake 1

Calling pandas.read_sql_query with a connection object created by sqlite3.connect but forgetting to close it, then later writing to the same database file and getting a 'database is locked' error, or reusing a cursor after the connection is closed.

Candidates learn the one-liner read_sql pattern and never practice connection lifecycle management, so they don't realize SQLite locks the file while a connection with an open transaction exists.

What to do instead

Use a context manager for the connection (with sqlite3.connect(path) as conn:) or wrap in try/finally with conn.close(). Commit or roll back before closing, and use pd.read_sql_query(sql, conn) with the connection object rather than a string. For SQLAlchemy, use create_engine and engine.connect() inside a with block.

Mistake 2

Building SQL with Python string concatenation or f-strings that interpolate user-supplied values, e.g. f"SELECT * FROM users WHERE name = '{name}'", which breaks on apostrophes and invites SQL injection.

Concatenation is the most obvious way to combine Python and SQL, and candidates do not connect the exam's ethical-handling and exception topics to query construction.

What to do instead

Use parameterized queries: cursor.execute('SELECT * FROM users WHERE name = ?', (name,)) for sqlite3, or %(name)s with a dict for psycopg2, or pass params= to pandas.read_sql_query. Never format values into the SQL string; only identifiers that cannot be parameterized should be validated against an allowlist.

Mistake 3

Treating missing data as zero: calling fillna(0) on a column of measurements or prices before computing the mean, or dropping rows with dropna() without checking how many rows that removes.

fillna(0) is the shortest fix for a NaN error and candidates prioritize making the code run over preserving the statistical meaning of the data.

What to do instead

First quantify missingness with df.isna().sum() and df.isna().mean(). Then choose deliberately: drop rows only if missingness is small and plausibly random; impute with mean/median for numeric or mode for categorical, documenting the choice; use interpolation for time series. Never impute 0 into a variable where 0 is a meaningful but wrong value.

Mistake 4

Assuming dtypes are correct after loading: doing arithmetic on a column that pandas read as object because it contained '1,200' or '$45' or a stray 'N/A', and getting concatenation or a TypeError instead of numbers.

read_csv infers types silently, and candidates inspect df.head() visually rather than checking df.dtypes, so string-typed numeric columns pass unnoticed until an operation fails.

What to do instead

After loading, run df.info() and df.dtypes, then coerce explicitly: pd.to_numeric(df['amount'], errors='coerce') after stripping currency symbols and commas with .str.replace(r'[$,]', '', regex=True), and pd.to_datetime(..., errors='coerce') for dates. Check how many values became NaN as a result.

Mistake 5

Confusing loc and chained indexing: using df[df['x'] > 0]['y'] = 0 or df['y'][mask] = 0 and getting a SettingWithCopyWarning with the assignment silently not applied to the original DataFrame.

Chained indexing reads naturally left to right, and candidates do not know that the intermediate object may be a copy, so the code appears to succeed.

What to do instead

Use a single .loc call for both selection and assignment: df.loc[df['x'] > 0, 'y'] = 0. If you intentionally work on a subset, take an explicit copy with .copy(). Treat SettingWithCopyWarning as an error to fix, not a warning to suppress.

Mistake 6

Using a Python for loop with iterrows() to compute a new column or aggregate over a large DataFrame, then running out of time or producing code the grader considers non-idiomatic.

Loops are the first tool programmers learn, and candidates do not yet think in terms of vectorized column operations.

What to do instead

Replace loops with vectorized expressions: df['total'] = df['price'] * df['qty'], boolean masks instead of if-statements inside loops, np.where for conditional columns, and groupby().agg() for grouped statistics. Reserve apply() for genuinely non-vectorizable logic and iterrows() for cases where row order and side effects matter.

Mistake 7

Writing GROUP BY queries that select non-aggregated columns not present in the GROUP BY clause, or filtering aggregated results with WHERE instead of HAVING, producing errors or wrong row counts.

Candidates mentally translate a SELECT they want into SQL without applying SQL's evaluation order, and WHERE-before-GROUP BY versus HAVING-after is easy to conflate.

What to do instead

Remember the logical order FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. Every non-aggregated selected column must appear in GROUP BY. Filter individual rows in WHERE and aggregated values in HAVING, e.g. SELECT dept, AVG(salary) FROM emp GROUP BY dept HAVING AVG(salary) > 50000. Use COUNT(*) versus COUNT(col) deliberately to control NULL handling.

Mistake 8

Reporting a p-value or confidence interval without stating the hypotheses and assumptions, or interpreting p = 0.03 as 'a 3% chance the null hypothesis is true', and treating a fail-to-reject as proof of no effect.

Candidates memorize the mechanics of scipy.stats.ttest_ind without the inferential framing, and the p-value's definition is genuinely counterintuitive.

What to do instead

State H0 and H1 in words before testing, check the assumptions (independence, approximate normality or large n, equal variances for Student's t versus Welch's), and phrase conclusions as 'evidence against H0 at the 5% level', not as the probability that H0 is true. Report the effect size and confidence interval alongside the p-value, and note that non-rejection is not evidence of equivalence.

Mistake 9

Evaluating a regression only with R-squared on the training data, or reporting accuracy/RMSE from model.fit without a held-out split, and then claiming the model predicts well.

R-squared on training data is the easiest number to print, and candidates have not internalized that in-sample fit is optimistic.

What to do instead

Split with train_test_split before fitting, fit only on the training set, and report metrics on the test set: RMSE/MAE for regression, R-squared as supplementary. Use cross_val_score for a more stable estimate. Check residual plots for non-linearity and heteroscedasticity, and compare against a baseline (predicting the mean) so the metric has context.

Mistake 10

Creating a Matplotlib figure with plt.plot(...) and plt.show() in a loop or across cells, so subsequent plots overlay the previous ones or the wrong axes get labeled, and never calling plt.savefig before show.

The stateful pyplot interface is what tutorials show first, and candidates do not distinguish the global current-figure state from an explicit Figure/Axes object.

What to do instead

Use the object-oriented API: fig, ax = plt.subplots(), then ax.plot(...), ax.set_xlabel(...), ax.set_title(...), and fig.savefig('out.png', dpi=150, bbox_inches='tight') before plt.show(). Call plt.close(fig) when generating many figures. For Seaborn, pass ax= explicitly when composing subplots.

Mistake 11

Writing a class whose __init__ performs I/O or validation that can fail, and letting the raw exception (FileNotFoundError, KeyError, ValueError) escape without a clear message, or catching it with a bare except: pass.

Candidates focus on getting the happy path working and treat exception handling as boilerplate, and bare except feels like a quick way to stop crashes.

What to do instead

Keep init cheap and predictable; do heavy work in explicit methods. Validate inputs in init and raise ValueError with a specific message. Catch only exceptions you can handle, in the narrowest scope, and either recover meaningfully or re-raise with context using raise CustomError('...') from exc. Never use bare except or silently swallow errors.

Mistake 12

Scraping or calling an API and immediately redistributing or storing personal data (names, emails, user IDs) in the analysis dataset without checking the site's terms, robots.txt, rate limits, or whether the data should be anonymized.

The technical path of requests.get plus parsing works, so candidates treat legality and ethics as outside the exercise, and the exam's ethical-handling domain is easy to under-prepare.

What to do instead

Before collecting, check robots.txt and terms of service, respect rate limits with time.sleep or a session with retries, send an identifying User-Agent, and prefer official APIs with documented terms over scraping. Minimize collected fields, hash or drop direct identifiers, and document the source, collection date, and consent basis in the notebook so the analysis is reproducible and defensible.