Skip to main content

Pandas - Fix Wrong Data Formats

Calculating read time…

Data rarely comes in perfect format. Dates might be stored as text, numbers might have currency symbols, or ages might be stored as strings. This tutorial shows you how to fix wrong data formats in Pandas and convert everything to the correct type for analysis.

We'll cover dates, numbers, currency, percentages, and mixed formats with plenty of practical examples!

Sample Dataset for Practice

Let's create a messy dataset with various format issues. This is what real-world data often looks like!

import pandas as pd
import numpy as np

# Create dataset with format issues
data = {
    'Employee_ID': ['E001', 'E002', 'E003', 'E004', 'E005', 'E006', 'E007', 'E008', 'E009', 'E010'],
    'Name': ['Alice Smith', 'Bob Jones', 'Charlie Brown', 'Diana Prince', 'Eve Wilson', 'Frank Miller', 'Grace Lee', 'Henry Ford', 'Ivy Chen', 'Jack Ryan'],
    'Age': ['28', '32', 'thirty-five', '29', '45', '33', '27', 'invalid', '38', '41'],
    'Salary': ['$50,000', '52000', '$55,500.50', '48000', '$60,000', 'USD 53000', '49,500', '$70000.00', '51000', '62,500'],
    'Bonus': ['10%', '15%', '12.5%', '8%', '20%', '10.5%', '15%', '18%', '12%', '14%'],
    'Join_Date': ['2023-01-15', '15/03/2023', 'Jan 20, 2023', '2023.05.10', '2022-12-01', '2023/07/22', '20-09-2023', 'March 15, 2023', '2023-11-08', '01-Feb-2023'],
    'Last_Login': ['2024-01-15 14:30:00', '2024-01-16 09:15', '2024/01/17 16:45:30', 'Jan 18, 2024 10:00', '2024-01-19', '2024-01-20T08:30:00', '21/01/2024 11:00', '2024.01.22 15:20', 'invalid', '2024-01-23'],
    'Hours_Worked': ['40.5', '38', '42.75', '40', 'thirty-seven', '39.5', '41', '40.25', '38.5', '40'],
    'Rating': ['4.5', '3.8', '4.2', '4.7', '3.9', 'four point five', '4.1', '4.3', '3.7', '4.6'],
    'Amount': ['1,234.56', '2345.67', '3,456.78', '4567.89', '5,678.90', '6789.01', '7,890.12', '8901.23', '9,012.34', '10,123.45']
}

df = pd.DataFrame(data)
print(df)

Dataset Preview:

Employee_ID Age Salary Bonus Join_Date
E001 '28' ⚠️ '$50,000' ⚠️ '10%' ⚠️ '2023-01-15'
E002 '32' ⚠️ '52000' ⚠️ '15%' ⚠️ '15/03/2023' ⚠️
E003 'thirty-five' ⚠️ '$55,500.50' ⚠️ '12.5%' ⚠️ 'Jan 20, 2023' ⚠️

💡 Notice: Everything looks like text (strings) when it should be numbers or dates!

1. Fix Numeric Format - Remove Currency Symbols

Salary columns often have $, commas, or currency codes. We need to strip these out and convert to numbers.

# Remove $ sign, commas, and 'USD' prefix
df['Salary_Clean'] = df['Salary'].str.replace('$', '', regex=False)
df['Salary_Clean'] = df['Salary_Clean'].str.replace(',', '', regex=False)
df['Salary_Clean'] = df['Salary_Clean'].str.replace('USD', '', regex=False)
df['Salary_Clean'] = df['Salary_Clean'].str.strip()  # Remove extra spaces

# Convert to numeric
df['Salary_Clean'] = pd.to_numeric(df['Salary_Clean'], errors='coerce')

print("Before and After:")
print(df[['Salary', 'Salary_Clean']].head())
print(f"\nData type changed: {df['Salary'].dtype} → {df['Salary_Clean'].dtype}")

Example Output:

Before and After:
        Salary  Salary_Clean
0     $50,000       50000.00
1       52000       52000.00
2  $55,500.50       55500.50
3       48000       48000.00
4     $60,000       60000.00

Data type changed: object → float64

💡 Pro Tip: Use errors='coerce' to convert invalid values to NaN instead of throwing errors!

2. Clean All Currency in One Step

Here's a faster way to clean currency using regex (regular expressions):

# Remove all non-numeric characters except decimal point
df['Salary_Clean'] = df['Salary'].str.replace(r'[^\d.]', '', regex=True)
df['Salary_Clean'] = pd.to_numeric(df['Salary_Clean'], errors='coerce')

print("Cleaned Salaries:")
print(df[['Name', 'Salary', 'Salary_Clean']])

Real-World Use Case: Financial reports, e-commerce data, and accounting exports often have currency symbols that need cleaning!

3. Convert Percentages to Decimals

Percentage values like "10%" need to be converted to 0.10 for calculations.

# Remove % sign and convert to decimal
df['Bonus_Decimal'] = df['Bonus'].str.replace('%', '', regex=False)
df['Bonus_Decimal'] = pd.to_numeric(df['Bonus_Decimal'], errors='coerce') / 100

print("Percentage to Decimal:")
print(df[['Name', 'Bonus', 'Bonus_Decimal']].head())

# Calculate actual bonus amount
df['Bonus_Amount'] = df['Salary_Clean'] * df['Bonus_Decimal']
print("\nBonus Calculations:")
print(df[['Name', 'Salary_Clean', 'Bonus_Decimal', 'Bonus_Amount']].head())

Example Output:

Percentage to Decimal:
         Name Bonus  Bonus_Decimal
0  Alice Smith   10%           0.10
1    Bob Jones   15%           0.15
2  Charlie...   12.5%          0.125

Bonus Calculations:
         Name  Salary_Clean  Bonus_Decimal  Bonus_Amount
0  Alice Smith      50000.0           0.10        5000.0
1    Bob Jones      52000.0           0.15        7800.0

4. Fix Age Format - Handle Text Numbers

Age might be stored as text or have invalid entries. Let's clean it properly.

# Convert age to numeric (invalid values become NaN)
df['Age_Clean'] = pd.to_numeric(df['Age'], errors='coerce')

# Show which ages failed to convert
invalid_ages = df[df['Age_Clean'].isna()]
print("Invalid Age Values:")
print(invalid_ages[['Name', 'Age', 'Age_Clean']])

# Fill invalid ages with median age
median_age = df['Age_Clean'].median()
df['Age_Clean'].fillna(median_age, inplace=True)

print(f"\nInvalid ages replaced with median: {median_age}")
print("\nCleaned Ages:")
print(df[['Name', 'Age', 'Age_Clean']].head(10))

Example Output:

Invalid Age Values:
           Name          Age  Age_Clean
2  Charlie Brown  thirty-five        NaN
7     Henry Ford      invalid        NaN

 Invalid ages replaced with median: 33.0

Cleaned Ages:
           Name          Age  Age_Clean
0   Alice Smith           28       28.0
2  Charlie Brown  thirty-five       33.0  ← Fixed!
7     Henry Ford      invalid       33.0  ← Fixed!

5. Convert Date Formats - The Right Way

Dates are tricky because they come in many formats. Pandas to_datetime() is smart enough to handle most formats automatically!

# Convert various date formats to standard datetime
df['Join_Date_Clean'] = pd.to_datetime(df['Join_Date'], errors='coerce')

print("Date Format Conversion:")
print(df[['Name', 'Join_Date', 'Join_Date_Clean']].head(8))

# Check data type
print(f"\nData type: {df['Join_Date_Clean'].dtype}")

# Now you can extract useful information!
df['Join_Year'] = df['Join_Date_Clean'].dt.year
df['Join_Month'] = df['Join_Date_Clean'].dt.month
df['Join_Day_Name'] = df['Join_Date_Clean'].dt.day_name()

print("\nExtracted Date Components:")
print(df[['Name', 'Join_Date_Clean', 'Join_Year', 'Join_Month', 'Join_Day_Name']].head())

Example Output:

Date Format Conversion:
         Name     Join_Date Join_Date_Clean
0  Alice Smith  2023-01-15      2023-01-15
1    Bob Jones  15/03/2023      2023-03-15  ← Converted!
2  Charlie...   Jan 20, 2023    2023-01-20  ← Converted!
3  Diana Prince 2023.05.10      2023-05-10  ← Converted!

Data type: datetime64[ns]

Extracted Date Components:
         Name Join_Date_Clean  Join_Year  Join_Month Join_Day_Name
0  Alice Smith      2023-01-15       2023           1        Sunday
1    Bob Jones      2023-03-15       2023           3     Wednesday

Real-World Use Case: Calculate employee tenure, analyze trends by month, filter by date ranges!

6. Handle DateTime with Time Components

When you have both date and time, clean them together!

# Convert datetime with time component
df['Last_Login_Clean'] = pd.to_datetime(df['Last_Login'], errors='coerce')

print("DateTime Conversion:")
print(df[['Name', 'Last_Login', 'Last_Login_Clean']].head())

# Extract time components
df['Login_Hour'] = df['Last_Login_Clean'].dt.hour
df['Login_Minute'] = df['Last_Login_Clean'].dt.minute
df['Login_Date_Only'] = df['Last_Login_Clean'].dt.date
df['Login_Time_Only'] = df['Last_Login_Clean'].dt.time

print("\nExtracted Time Components:")
print(df[['Name', 'Last_Login_Clean', 'Login_Hour', 'Login_Date_Only', 'Login_Time_Only']].head())

Example Output:

Extracted Time Components:
         Name   Last_Login_Clean  Login_Hour Login_Date_Only Login_Time_Only
0  Alice Smith  2024-01-15 14:30          14      2024-01-15        14:30:00
1    Bob Jones  2024-01-16 09:15           9      2024-01-16        09:15:00

Pro Tip: Extract hour to analyze peak usage times, or day of week to find patterns!

7. Remove Commas from Large Numbers

Numbers with thousand separators (1,234.56) need cleaning before calculations.

# Remove commas from numbers
df['Amount_Clean'] = df['Amount'].str.replace(',', '', regex=False)
df['Amount_Clean'] = pd.to_numeric(df['Amount_Clean'], errors='coerce')

print("Before and After:")
print(df[['Name', 'Amount', 'Amount_Clean']].head())

# Now you can do calculations!
total_amount = df['Amount_Clean'].sum()
average_amount = df['Amount_Clean'].mean()

print(f"\nTotal Amount: ${total_amount:,.2f}")
print(f"Average Amount: ${average_amount:,.2f}")

Example Output:

Before and After:
         Name      Amount  Amount_Clean
0  Alice Smith   1,234.56       1234.56
1    Bob Jones   2345.67       2345.67
2  Charlie...    3,456.78       3456.78

Total Amount: $54,999.95
Average Amount: $5,499.99

8. Convert Text to Proper Data Types

Sometimes you need to explicitly convert data types for better performance and accuracy.

# Convert Hours_Worked to float
df['Hours_Worked_Clean'] = pd.to_numeric(df['Hours_Worked'], errors='coerce')

# Convert Rating to float
df['Rating_Clean'] = pd.to_numeric(df['Rating'], errors='coerce')

# Convert Employee_ID to category (saves memory!)
df['Employee_ID'] = df['Employee_ID'].astype('category')

print("Data Types After Conversion:")
print(df[['Hours_Worked_Clean', 'Rating_Clean', 'Employee_ID']].dtypes)

print("\nMemory Usage Comparison:")
print(f"Before: object type uses more memory")
print(f"After: float64, category types use less memory")

Real-World Use Case: Converting to proper types speeds up calculations and reduces memory usage on large datasets!

9. Create a Complete Format Cleaning Function

Here's a reusable function that cleans all common format issues at once!

def clean_data_formats(df):
    """Clean common data format issues"""
    
    print("=== CLEANING DATA FORMATS ===\n")
    df_clean = df.copy()
    
    # 1. Clean currency columns
    currency_columns = ['Salary', 'Amount']
    for col in currency_columns:
        if col in df_clean.columns:
            df_clean[f'{col}_Clean'] = df_clean[col].str.replace(r'[^\d.]', '', regex=True)
            df_clean[f'{col}_Clean'] = pd.to_numeric(df_clean[f'{col}_Clean'], errors='coerce')
            print(f" Cleaned {col}: Removed currency symbols and commas")
    
    # 2. Clean percentage columns
    percentage_columns = ['Bonus']
    for col in percentage_columns:
        if col in df_clean.columns:
            df_clean[f'{col}_Decimal'] = df_clean[col].str.replace('%', '', regex=False)
            df_clean[f'{col}_Decimal'] = pd.to_numeric(df_clean[f'{col}_Decimal'], errors='coerce') / 100
            print(f" Cleaned {col}: Converted to decimal")
    
    # 3. Clean numeric columns
    numeric_columns = ['Age', 'Hours_Worked', 'Rating']
    for col in numeric_columns:
        if col in df_clean.columns:
            df_clean[f'{col}_Clean'] = pd.to_numeric(df_clean[col], errors='coerce')
            # Fill NaN with median
            median_val = df_clean[f'{col}_Clean'].median()
            df_clean[f'{col}_Clean'].fillna(median_val, inplace=True)
            print(f" Cleaned {col}: Converted to numeric, filled missing with median")
    
    # 4. Clean date columns
    date_columns = ['Join_Date', 'Last_Login']
    for col in date_columns:
        if col in df_clean.columns:
            df_clean[f'{col}_Clean'] = pd.to_datetime(df_clean[col], errors='coerce')
            print(f" Cleaned {col}: Converted to datetime")
    
    print("\n=== SUMMARY ===")
    print(f"Original columns: {len(df.columns)}")
    print(f"After cleaning: {len(df_clean.columns)}")
    print(f"New clean columns created: {len(df_clean.columns) - len(df.columns)}")
    
    return df_clean

# Use the function
df_final = clean_data_formats(df)
print("\n" + "="*50)
print(df_final.dtypes)

Example Output:

=== CLEANING DATA FORMATS ===

 Cleaned Salary: Removed currency symbols and commas
 Cleaned Amount: Removed currency symbols and commas
 Cleaned Bonus: Converted to decimal
 Cleaned Age: Converted to numeric, filled missing with median
 Cleaned Hours_Worked: Converted to numeric, filled missing with median
 Cleaned Rating: Converted to numeric, filled missing with median
 Cleaned Join_Date: Converted to datetime
 Cleaned Last_Login: Converted to datetime

=== SUMMARY ===
Original columns: 10
After cleaning: 18
New clean columns created: 8

10. Handle Special Cases - Mixed Formats

Sometimes one column has multiple format issues. Here's how to handle complex scenarios:

# Example: Phone numbers in different formats
phone_data = {
    'Name': ['Alice', 'Bob', 'Charlie', 'Diana'],
    'Phone': ['(123) 456-7890', '123-456-7890', '1234567890', '+1-123-456-7890']
}
df_phone = pd.DataFrame(phone_data)

# Clean phone numbers - keep only digits
df_phone['Phone_Clean'] = df_phone['Phone'].str.replace(r'[^\d]', '', regex=True)

# Format consistently as XXX-XXX-XXXX
df_phone['Phone_Formatted'] = df_phone['Phone_Clean'].str[:3] + '-' + \
                               df_phone['Phone_Clean'].str[3:6] + '-' + \
                               df_phone['Phone_Clean'].str[6:10]

print("Phone Number Cleaning:")
print(df_phone)

Example Output:

Phone Number Cleaning:
      Name              Phone Phone_Clean Phone_Formatted
0    Alice   (123) 456-7890  1234567890   123-456-7890
1      Bob    123-456-7890   1234567890   123-456-7890
2  Charlie      1234567890   1234567890   123-456-7890
3    Diana  +1-123-456-7890  11234567890  112-345-67890  ← Note: +1 issue!

Common Format Cleaning Patterns - Quick Reference

# Remove currency symbols and commas
df['price_clean'] = df['price'].str.replace(r'[^\d.]', '', regex=True)
df['price_clean'] = pd.to_numeric(df['price_clean'], errors='coerce')

# Convert percentage to decimal
df['percent_decimal'] = df['percent'].str.replace('%', '').astype(float) / 100

# Convert text numbers to numeric
df['age_clean'] = pd.to_numeric(df['age'], errors='coerce')

# Convert dates (handles multiple formats)
df['date_clean'] = pd.to_datetime(df['date'], errors='coerce')

# Remove commas from large numbers
df['number_clean'] = df['number'].str.replace(',', '').astype(float)

# Extract only digits
df['digits_only'] = df['text'].str.replace(r'[^\d]', '', regex=True)

# Convert to specific date format
df['date_formatted'] = df['date_clean'].dt.strftime('%Y-%m-%d')

# Fill invalid conversions with median/mean
df['column_clean'].fillna(df['column_clean'].median(), inplace=True)

Best Practices for Format Cleaning

  • Always check data types first: Use df.dtypes to see what needs fixing
  • Use errors='coerce': Converts invalid values to NaN instead of crashing
  • Keep original columns: Create new cleaned columns so you can compare
  • Validate after cleaning: Check min/max, unique values to ensure correctness
  • Document your cleaning: Write comments explaining what you cleaned and why
  • Handle missing values: Decide whether to fill, drop, or flag them
  • Test on sample data: Try cleaning on a few rows first before the whole dataset

Remember:

  • Use pd.to_numeric() for numbers
  • Use pd.to_datetime() for dates
  • Use str.replace() for text cleaning
  • Use errors='coerce' to handle invalid values gracefully
  • Always validate your cleaned data before analysis

Comments