PySpark - Convert String to Integer

Type conversion is a fundamental operation in any PySpark data pipeline. String-to-integer conversion specifically comes up constantly when loading CSV files (where everything defaults to strings),...

Key Insights

  • Use the cast() method with IntegerType() or LongType() for type-safe conversions that handle invalid values gracefully by returning null
  • PySpark converts non-numeric strings to null rather than throwing errors, requiring explicit validation logic when data quality matters
  • Batch convert multiple columns using list comprehensions with withColumn() or dictionary unpacking with select() to minimize code duplication

Introduction

Type conversion is a fundamental operation in any PySpark data pipeline. String-to-integer conversion specifically comes up constantly when loading CSV files (where everything defaults to strings), integrating with external systems that send numeric data as text, or cleaning messy datasets where numeric columns got imported incorrectly.

The cost of ignoring data types is real: string comparisons where you need numeric ordering, failed joins due to type mismatches, and operations like aggregations that simply won’t work on string representations of numbers. Let’s look at a typical scenario:

from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, StringType

spark = SparkSession.builder.appName("StringToInt").getOrCreate()

# Simulating CSV import where numeric columns come in as strings
data = [
    ("1001", "250", "5"),
    ("1002", "175", "3"),
    ("1003", "500", "8"),
    ("1004", "invalid", "2")
]

schema = StructType([
    StructField("order_id", StringType(), True),
    StructField("amount", StringType(), True),
    StructField("quantity", StringType(), True)
])

df = spark.createDataFrame(data, schema)
df.printSchema()

This prints string types for all columns, even though amount and quantity should clearly be numeric. You can’t sum these, can’t do mathematical comparisons, and any analytics will fail.

Using cast() Method

The cast() method is your primary tool for type conversion in PySpark. It’s explicit, readable, and handles edge cases gracefully. You can use it with either string type names or imported PySpark types.

Here’s the basic approach:

from pyspark.sql.functions import col
from pyspark.sql.types import IntegerType, LongType

# Method 1: Cast using string type name
df_converted = df.withColumn("amount", col("amount").cast("int"))

# Method 2: Cast using IntegerType (more explicit)
df_converted = df.withColumn("amount", col("amount").cast(IntegerType()))

df_converted.printSchema()
# amount is now IntegerType

The difference between IntegerType() and LongType() matters for range. Integers in PySpark are 32-bit signed (-2,147,483,648 to 2,147,483,647), while Longs are 64-bit. For IDs, large monetary values in cents, or any number that might exceed 2 billion, use LongType():

# For large numbers, use LongType
df_with_long = df.withColumn("order_id", col("order_id").cast(LongType()))

You can also cast and rename in one operation:

# Cast and create new column with alias
df_aliased = df.withColumn("amount_int", col("amount").cast(IntegerType()))

# Or use alias() method
df_aliased2 = df.select(
    col("order_id"),
    col("amount").cast(IntegerType()).alias("amount_int"),
    col("quantity")
)

Using selectExpr() with SQL CAST

If you’re more comfortable with SQL syntax or working with a team that prefers SQL-style transformations, selectExpr() lets you write SQL expressions directly:

# SQL-style casting
df_sql_cast = df.selectExpr(
    "order_id",
    "CAST(amount AS INT) as amount",
    "CAST(quantity AS INT) as quantity"
)

df_sql_cast.printSchema()

This is functionally identical to using cast() but can be more concise when you’re doing multiple transformations. It’s particularly useful when migrating from SQL-based ETL processes to PySpark:

# Complex transformation with SQL expressions
df_complex = df.selectExpr(
    "CAST(order_id AS BIGINT) as order_id",
    "CAST(amount AS INT) as amount",
    "CAST(quantity AS INT) as quantity",
    "CAST(amount AS INT) * CAST(quantity AS INT) as total_value"
)

The choice between cast() and SQL-style casting is mostly stylistic, though cast() offers better IDE support and type checking.

Handling Invalid Values and Null Conversion

Here’s where PySpark’s behavior is critical to understand: invalid conversions return null, not errors. This is different from pandas, which would raise an exception. Let’s see this in action:

# Show what happens with invalid data
df.select(
    col("order_id"),
    col("amount"),
    col("amount").cast(IntegerType()).alias("amount_int")
).show()

# Output shows "invalid" becomes null
# +--------+-------+----------+
# |order_id| amount|amount_int|
# +--------+-------+----------+
# |    1001|    250|       250|
# |    1002|    175|       175|
# |    1003|    500|       500|
# |    1004|invalid|      null|
# +--------+-------+----------+

For production pipelines, you need explicit handling. Here are three strategies:

Strategy 1: Filter out invalid rows before conversion

from pyspark.sql.functions import col

# Remove rows where amount is not numeric
df_clean = df.filter(col("amount").rlike("^[0-9]+$"))
df_clean_converted = df_clean.withColumn("amount", col("amount").cast(IntegerType()))

Strategy 2: Use conditional logic to handle invalids

from pyspark.sql.functions import when

# Replace invalid values with a default (e.g., 0)
df_with_default = df.withColumn(
    "amount",
    when(col("amount").rlike("^[0-9]+$"), col("amount").cast(IntegerType()))
    .otherwise(0)
)

Strategy 3: Flag invalid rows for review

# Add a validation column
df_validated = df.withColumn("amount_int", col("amount").cast(IntegerType()))
df_validated = df_validated.withColumn(
    "is_valid",
    when(col("amount_int").isNull() & col("amount").isNotNull(), False)
    .otherwise(True)
)

# Separate valid and invalid records
valid_records = df_validated.filter(col("is_valid") == True)
invalid_records = df_validated.filter(col("is_valid") == False)

Converting Multiple Columns at Once

Converting columns one by one is tedious and error-prone. Here are efficient patterns for batch conversion:

Using list comprehension with reduce:

from functools import reduce
from pyspark.sql import DataFrame

# Columns to convert
cols_to_convert = ["amount", "quantity"]

# Apply cast to multiple columns
df_multi = reduce(
    lambda df, col_name: df.withColumn(col_name, col(col_name).cast(IntegerType())),
    cols_to_convert,
    df
)

Using select() with list comprehension:

# More readable approach
int_columns = ["amount", "quantity"]
other_columns = ["order_id"]

df_multi2 = df.select(
    [col(c) for c in other_columns] +
    [col(c).cast(IntegerType()).alias(c) for c in int_columns]
)

Dynamic column selection with dictionary:

# Define type mappings
type_mapping = {
    "order_id": LongType(),
    "amount": IntegerType(),
    "quantity": IntegerType()
}

# Apply all conversions
df_typed = df.select([
    col(col_name).cast(dtype).alias(col_name)
    for col_name, dtype in type_mapping.items()
])

For all columns matching a pattern:

# Convert all columns ending with '_id' to LongType
id_columns = [field.name for field in df.schema.fields if field.name.endswith('_id')]

df_ids_converted = reduce(
    lambda df, col_name: df.withColumn(col_name, col(col_name).cast(LongType())),
    id_columns,
    df
)

Performance Considerations and Best Practices

Type conversion isn’t free, but the cost is usually negligible compared to I/O and shuffles. Still, follow these practices:

Convert early in your pipeline: Don’t repeatedly cast the same column. Do it once after loading data:

# Good: Convert once at the start
df_typed = (df
    .withColumn("amount", col("amount").cast(IntegerType()))
    .withColumn("quantity", col("quantity").cast(IntegerType()))
)

# Then use df_typed for all downstream operations
result = df_typed.filter(col("amount") > 100).groupBy("order_id").sum("quantity")

# Bad: Casting repeatedly
result = (df
    .filter(col("amount").cast(IntegerType()) > 100)  # Cast 1
    .groupBy("order_id")
    .agg(sum(col("quantity").cast(IntegerType())))    # Cast 2
)

Cache after conversion if reusing: If you’ll use the converted DataFrame multiple times, cache it:

df_typed = df.withColumn("amount", col("amount").cast(IntegerType()))
df_typed.cache()

# Multiple operations on cached data
summary1 = df_typed.groupBy("order_id").sum("amount")
summary2 = df_typed.filter(col("amount") > 100).count()

Validate before converting in production: Don’t let silent null conversions corrupt your data:

# Production pattern: validate, convert, verify
from pyspark.sql.functions import sum as spark_sum

original_count = df.count()
df_converted = df.withColumn("amount", col("amount").cast(IntegerType()))
null_count = df_converted.filter(col("amount").isNull()).count()

if null_count > 0:
    print(f"Warning: {null_count} rows failed conversion")
    # Log, alert, or reject the batch

Use appropriate integer types: Don’t default to IntegerType() for everything. IDs and timestamps often need LongType():

# Choose based on expected range
df_optimized = df.select(
    col("order_id").cast(LongType()),      # IDs can be large
    col("amount").cast(IntegerType()),      # Money in dollars fits in int
    col("quantity").cast(IntegerType())     # Quantities are usually small
)

String-to-integer conversion in PySpark is straightforward, but production-quality implementations require attention to invalid data handling, efficient batch operations, and appropriate type selection. The patterns shown here will handle the vast majority of real-world scenarios you’ll encounter.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.