Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›PySpark›Handling Null Values

Transformations

Handling Null Values

Null values represent missing or unknown data in your PySpark DataFrames, requiring deliberate handling to prevent silent data loss or logic failures. Proper management ensures your transformations remain predictable, stable, and accurate during large-scale distributed computations. You must address nulls whenever performing aggregations, joining datasets, or preparing features for downstream analytical processing.

Understanding Null Semantics

In PySpark, a null value is not the same as zero or an empty string; it is a sentinel value indicating the total absence of information for a specific record. Understanding nulls is critical because most binary operators and arithmetic expressions in the engine follow 'null-propagation' logic. If you attempt to add, multiply, or compare a null value with a non-null value, the resulting output will almost always be null. This happens because the engine cannot determine the result of an operation involving an unknown quantity. By recognizing that nulls act as a 'contagion' in your pipeline, you can anticipate where your row counts might unexpectedly drop after applying certain transformations. Treating nulls correctly is not merely about cleanup; it is about maintaining the integrity of your schema and ensuring that the statistical distributions of your data remain representative of the real-world scenarios you are modeling.

from pyspark.sql import SparkSession
from pyspark.sql.functions import col

spark = SparkSession.builder.getOrCreate()
data = [(1, 100), (2, None), (3, 300)]
df = spark.createDataFrame(data, ["id", "value"])

# Adding null results in a null, demonstrating null-propagation
df.withColumn("total", col("value") + 10).show()

Dropping Null Records

The simplest way to handle missing data is to remove the records entirely using the na.drop() method. This approach is statistically valid when the missingness is 'Missing Completely At Random' (MCAR) and the volume of dropped data does not significantly bias your results. When you call drop(), PySpark scans the dataset for nulls based on the configuration you provide. By default, it drops a row if any column contains a null. However, you can pass a subset of columns to the function to be more specific. This is highly performant because it avoids expensive shuffling or complex value calculations, but it comes with the danger of shrinking your dataset to an unusable size. Always evaluate the percentage of data lost before finalizing this strategy, as removing rows is destructive and cannot be undone once the pipeline proceeds to subsequent stages of the transformation chain.

# Drop rows if ANY column has null
df.na.drop().show()

# Drop rows only if 'value' is null
df.na.drop(subset=["value"]).show()

Imputation with Fillna

Often, dropping data is not feasible, so we use imputation to replace nulls with meaningful placeholders. The na.fill() method allows you to substitute nulls with constants, such as zeros for missing numeric counts or 'Unknown' for missing category strings. The reason this works is that you are enforcing a business logic rule to normalize the data schema before it reaches downstream consumers. Choosing the correct fill value is vital; for instance, filling a missing 'price' column with zero might inflate your revenue calculations, whereas using the global average or median could provide a more stable estimate. PySpark optimizes this replacement process internally, which is generally more efficient than manual case-when statements. By standardizing these null values into usable inputs, you prevent your machine learning models or reporting tools from crashing due to unexpected input types or null pointers.

# Fill nulls in numeric columns with 0
df.na.fill({"value": 0}).show()

# Fill nulls in strings with a default label
df.na.fill("N/A", subset=["value"]).show()

Conditional Replacement with Coalesce

The coalesce function provides a more sophisticated way to handle nulls by allowing you to prioritize values across multiple columns. It works by returning the first non-null argument encountered in the list. This is extremely powerful for scenarios involving fallbacks—for example, if you have a 'home_phone' column and a 'mobile_phone' column, you might want to create a 'primary_contact' field by selecting the first one that is not null. Unlike simple filling, coalesce is dynamic and depends on the relationship between columns, making it an essential tool for data cleaning where information exists in various sparse fields. By stacking options in the order of preference, you retain the highest quality data while gracefully handling the absence of information in secondary locations, ensuring that your final result set remains robust and information-dense regardless of individual record sparsity.

from pyspark.sql.functions import coalesce, lit

# Define primary contact using coalesce
# If value is null, use the literal 999 as a fallback
df.withColumn("final_value", coalesce(col("value"), lit(999))).show()

Nulls in Aggregations

Aggregations in PySpark, such as sum, avg, or count, behave differently when nulls are present. Most aggregate functions ignore null values entirely; for instance, the average function calculates the mean using only the existing non-null values as the denominator. This is usually the desired behavior, but it can be misleading if the missing data is not truly random. If you need to count nulls specifically to perform data quality checks, you must explicitly use count(col) or count_if patterns. When performing group-by operations, nulls are treated as their own unique grouping key. This means that if you have records with nulls in your grouping key, they will form a separate group in the final output. Managing this behavior is crucial for accurate reporting, as ignoring the existence of these 'null-groups' could lead to undercounting your overall population during your final business metrics extraction.

from pyspark.sql.functions import count, avg

# avg() ignores nulls in the calculation
df.select(avg("value")).show()

# count(col) ignores nulls, count(*) includes them
df.agg(count("value").alias("non_null_count"), count("*").alias("total_rows")).show()

Key points

  • Null values act as unknown placeholders and trigger null-propagation in binary operations.
  • The na.drop() method removes rows containing nulls but must be used carefully to avoid significant data loss.
  • Imputation with na.fill() allows developers to replace missing values with business-appropriate defaults.
  • The coalesce function selects the first non-null value from a list, enabling sophisticated fallback logic.
  • Aggregate functions like avg() typically ignore nulls, which can skew results if data is not missing at random.
  • Group-by operations treat nulls as a distinct bucket, resulting in a separate group for missing keys.
  • Always perform a row count comparison before and after dropping nulls to understand the impact of your transformation.
  • Understanding the difference between nulls and zero values is essential for writing accurate conditional logic.

Common mistakes

  • Mistake: Using == null to filter null values. Why it's wrong: In PySpark, equality operators do not evaluate to True for nulls. Fix: Use the .isNull() or .isNotNull() column methods.
  • Mistake: Assuming count() includes null values in a column. Why it's wrong: The count(col) function ignores null values, whereas count(*) counts rows regardless of nulls. Fix: Use count('*') to get total row count or count(col) specifically for non-null values.
  • Mistake: Trying to perform arithmetic operations on null values without handling them. Why it's wrong: Operations like (col('a') + col('b')) result in null if either value is null. Fix: Use .fillna() or .when().otherwise() to provide default values before calculation.
  • Mistake: Using dropna() without specifying subset columns. Why it's wrong: By default, dropna() drops any row containing at least one null value across the entire schema, potentially losing massive amounts of data. Fix: Use .dropna(subset=['target_column']) to be precise.
  • Mistake: Confusing None with an empty string or NaN. Why it's wrong: None (null) is a missing value, while "" is a character string and NaN is a floating point representation. Fix: Check for nulls specifically with .isNull() and address empty strings or NaNs as separate cleaning tasks.

Interview questions

What is the simplest way to remove rows containing null values in PySpark?

The simplest method to remove rows with null values is by using the 'dropna()' method on a DataFrame. You can simply call 'df.dropna()' to remove any row where at least one column has a null value. If you want more control, you can specify a subset of columns, for instance 'df.dropna(subset=['column_name'])', which only drops rows if that specific column is null. This is crucial for data cleaning because downstream transformations, such as mathematical aggregations or joins, can fail or produce skewed results if unexpected nulls are present in your datasets.

How can you replace null values in PySpark using the fillna method?

To replace null values, we use the 'fillna()' method, which allows you to substitute nulls with a specific value, such as zero for numeric columns or an empty string for text. For example, 'df.fillna(0, subset=['sales_column'])' replaces nulls with zero. This is vital in data engineering because it ensures data type consistency and prevents errors in arithmetic operations. By providing a default value, you maintain data continuity, ensuring that analytical models do not treat nulls as missing entries, which could otherwise lead to incorrect statistical summaries or broken feature engineering pipelines.

How do you detect null values in a PySpark DataFrame without dropping or filling them?

To detect nulls, you typically use the 'isNull()' function combined with the 'filter()' method. You can write 'df.filter(df['column'].isNull()).show()' to isolate rows with nulls for inspection. Alternatively, if you want a count, you can chain 'count()' after the filter. Understanding where nulls originate is essential for data quality auditing. By identifying these records, you can investigate if the upstream ingestion process is missing data or if the source system is providing incomplete records, allowing you to implement better validation logic early in the pipeline.

Compare using 'dropna' versus 'fillna' in a data pipeline context. When should you choose one over the other?

Choosing between 'dropna' and 'fillna' depends on your business requirement regarding data volume and information loss. Use 'dropna' when the presence of a null renders the entire record useless or invalid for the target analysis. Conversely, use 'fillna' when you want to preserve the record count and can safely impute a value, such as filling missing 'count' data with zero. 'dropna' reduces your dataset size, which is safer if the missing data is critical, whereas 'fillna' maintains dataset size but risks introducing bias if the imputation strategy does not accurately reflect the underlying reality of the data distribution.

How can you use the 'when' and 'otherwise' functions to create custom null-handling logic?

The 'when' and 'otherwise' functions, found in 'pyspark.sql.functions', provide a programmatic way to handle nulls conditionally. You would use 'df.withColumn('new_col', when(df['col'].isNull(), 'Default').otherwise(df['col']))'. This approach is superior to 'fillna' when you need context-aware replacement logic, such as filling a null in one column based on the value of a different column. It allows for complex business rules to be embedded directly into the transformation layer of your PySpark job, ensuring that data is normalized according to specific project needs rather than simple static replacement.

Explain how PySpark handles null values during Join operations, and how can you mitigate unexpected issues?

In PySpark, null values do not match other null values during standard inner join operations; they are essentially ignored. This often leads to data loss if your join keys contain nulls. To mitigate this, you should either clean your join keys beforehand by replacing nulls with a sentinel value or use a 'left_outer' join to preserve records. Additionally, you can explicitly join on nulls by using an expression like 'df1.join(df2, (df1.id == df2.id) | (df1.id.isNull() & df2.id.isNull()))'. This is a more advanced technique that ensures data integrity when dealing with sparse or dirty join keys.

All PySpark interview questions →

Check yourself

1. If you perform a left join between two DataFrames on a join key that contains nulls on the left side, what happens?

  • A.The rows with null keys are dropped
  • B.The rows with null keys are matched to nulls on the right
  • C.The join operation fails with an AnalysisException
  • D.The nulls are converted to zeros automatically
Show answer

B. The rows with null keys are matched to nulls on the right
In PySpark, nulls in join keys do not match other nulls, and they are preserved in a left join. Option 0 is wrong because inner joins drop null keys, not left joins. Option 2 is incorrect as this is standard behavior. Option 3 is false as no automatic casting occurs.

2. Which approach is most efficient to replace null values in a specific numeric column with the column mean?

  • A.Use a UDF to calculate the mean and replace values
  • B.Collect the mean to the driver and use .fillna()
  • C.Join the DataFrame with a pre-calculated mean DataFrame
  • D.Use .na.replace() with a list of nulls
Show answer

B. Collect the mean to the driver and use .fillna()
Collecting the mean to the driver is the standard, performant pattern. UDFs (Option 0) are slower due to serialization. Joining (Option 2) is overkill. Option 3 is syntactically incorrect for filling nulls.

3. How does count(col('x')) behave when column 'x' contains null values?

  • A.It treats nulls as zero
  • B.It throws an error
  • C.It excludes null values from the count
  • D.It counts null values as valid entries
Show answer

C. It excludes null values from the count
The count(col) function specifically counts non-null values. Option 0 and 3 are wrong because nulls are ignored. Option 1 is incorrect as Spark handles nulls without crashing.

4. When using .when(col('x').isNull(), 0).otherwise(col('x')), what is the primary impact on the resulting DataFrame schema?

  • A.The column becomes nullable if it wasn't already
  • B.The column remains nullable regardless of the replacement
  • C.The column becomes non-nullable because all nulls are filled
  • D.The column data type changes to string
Show answer

B. The column remains nullable regardless of the replacement
PySpark schema inference typically keeps columns as nullable unless explicitly cast otherwise; replacing values does not change the schema metadata. Options 0, 2, and 3 misinterpret Spark's rigid schema handling.

5. Which method should you use to remove rows where 'col_a' is null OR 'col_b' is null?

  • A..dropna(subset=['col_a', 'col_b'], how='any')
  • B..dropna(subset=['col_a', 'col_b'], how='all')
  • C..filter(col('col_a').isNotNull() && col('col_b').isNotNull())
  • D..drop(cols=['col_a', 'col_b'])
Show answer

A. .dropna(subset=['col_a', 'col_b'], how='any')
Using 'any' in dropna removes the row if any of the specified columns are null. Option 1 is wrong because 'all' only removes rows if both are null. Option 2 is invalid syntax. Option 3 removes the columns entirely, not the rows.

Take the full PySpark quiz →

← PreviousUDFs — User Defined FunctionsNext →String and Date Functions

PySpark

26 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app