All 57 lessons
Lesson 5 of 57 · Getting Started
Strings in Python: Text Handling for Analysts
By Skillancy Editorial Team
Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).
Numbers are only half of analytics. Customer names, city spellings, product descriptions, email addresses, order notes and survey comments are all text, and real data is full of text that is messy: extra spaces, inconsistent capitals, strange separators. A large part of an analyst's time is spent turning that text into something usable.
In Python, text is handled by the str (string) type. Strings come with dozens of built-in tools, called methods, for searching, cleaning, splitting and formatting. This lesson teaches the ones you will use every week, with FreshBasket examples. The same ideas return in pandas, where the .str accessor applies them to a whole column at once.
Creating strings
A string is a sequence of characters wrapped in quotes. Single quotes and double quotes are equivalent, so choose whichever avoids escaping:
city = "Bengaluru"
shop = 'FreshBasket'
quote = "It's fresh" # a double-quoted string can contain an apostrophe
note = 'He said "hurry"' # and vice versaTriple quotes (""" or ''') create a string that spans several lines, which is handy for messages and SQL queries. A backslash starts an escape sequence: \n is a new line, \t is a tab, and \\ is a literal backslash.
Indexing and slicing
Every character has a position called an index, starting at 0, not 1. Negative indexes count from the end: -1 is the last character.
F r e s h B a s k e t
0 1 2 3 4 5 6 7 8 9 10
-11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1A slice text[start:stop] takes characters from start up to, but not including, stop. Leave out start to begin at the start, leave out stop to go to the end, and add a third number for a step: text[::2] takes every second character, and text[::-1] reverses the string.
Worked examples
Example 1: Length, indexing and slicing
Question: For the order id O02299, extract the prefix letter and the numeric part, and check the length.
order_id = "O02299"
print(len(order_id))
print(order_id[0])
print(order_id[1:])
print(order_id[-2:])
print(order_id[::-1])Output
6
O
02299
99
99220OHow it works: len() counts characters. order_id[0] is the first character. order_id[1:] starts at index 1 and runs to the end, which drops the letter. order_id[-2:] starts two from the end. order_id[::-1] reads the string backwards by stepping by -1.
Reading the result: The id has 6 characters, begins with O, and its number part is 02299. Slicing never raises an error for out-of-range ends; it simply returns what exists. Indexing a single position that does not exist, such as order_id[10], does raise an IndexError.
Example 2: Changing case
Question: Names arrive as "aarav SHARMA". Make them consistent.
raw_name = "aarav SHARMA"
print(raw_name.upper())
print(raw_name.lower())
print(raw_name.title())
print(raw_name.capitalize())
print(raw_name.swapcase())Output
AARAV SHARMA
aarav sharma
Aarav Sharma
Aarav sharma
AARAV sharmaHow it works: Each method returns a new string and leaves the original untouched. title() capitalises the first letter of every word, which is what you want for names and cities, whereas capitalize() only changes the very first letter of the whole string.
Reading the result: title() gives Aarav Sharma, the clean form. Case normalisation is also the first step when comparing text, because "Delhi" and "delhi" are different strings to Python.
Example 3: Removing extra spaces
Question: A city field arrives as " Mumbai \n". How do I clean it?
messy_city = " Mumbai \n"
print(repr(messy_city))
print(repr(messy_city.strip()))
print(repr(messy_city.lstrip()))
print(repr(messy_city.rstrip()))
print("--Pune--".strip("-"))Output
' Mumbai \n'
'Mumbai'
'Mumbai \n'
' Mumbai'
PuneHow it works: strip() removes whitespace (spaces, tabs and new lines) from both ends. lstrip() and rstrip() clean only the left or right. Giving strip a string of characters removes those characters instead. We wrapped each result in repr() so that the hidden spaces and the \n are visible.
Reading the result: The original has leading spaces and a trailing newline; after strip() we have just 'Mumbai'. Stripping is usually the first cleaning step for any text column, because invisible whitespace is a classic reason why two "identical" values do not match.
Example 4: Replacing and standardising
Question: Standardise city variants "Bangalore" and "Bengalore" to "Bengaluru", and turn phone numbers into plain digits.
city = "Bangalore"
fixed_city = city.replace("Bangalore", "Bengaluru")
print(fixed_city)
phone = "+91 98765-43210"
digits = phone.replace("+91", "").replace(" ", "").replace("-", "")
print(digits, len(digits))Output
Bengaluru
9876543210 10How it works: replace(old, new) swaps every occurrence of old with new. Methods can be chained: replace(...).replace(...) applies each replacement in order to the result of the previous one.
Reading the result: The city is now Bengaluru, and the phone number is the 10-digit string 9876543210. For many replacements a loop or a dictionary is neater, and for pattern-based cleaning you will use regular expressions later in the tutorial.
Example 5: Splitting and joining
Question: An address line is "Koramangala, Bengaluru, Karnataka, 560034". Split it into parts, then rebuild it with a different separator.
address = "Koramangala, Bengaluru, Karnataka, 560034"
parts = address.split(", ")
print(parts)
print(len(parts), parts[-1])
print(" | ".join(parts))
print("a-b-c".split("-", 1))Output
['Koramangala', 'Bengaluru', 'Karnataka', '560034']
4 560034
Koramangala | Bengaluru | Karnataka | 560034
['a', 'b-c']How it works: split(separator) cuts a string into a list of pieces wherever the separator occurs, and separator.join(list) does the reverse, gluing pieces together with the separator between them. An optional second argument limits the number of splits.
Reading the result: The address broke into 4 parts, and the last is the pincode 560034. Notice that join is called on the separator, not on the list, which surprises most beginners. Called with no argument, split() splits on any whitespace and ignores repeated spaces, which makes it ideal for cleaning.
Example 6: Searching inside text
Question: From the email "priya.sharma42@example.com", find the position of @, check the domain, and count the dots.
email = "priya.sharma42@example.com"
print(email.find("@"))
print(email.find("#"))
print(email.startswith("priya"), email.endswith(".com"))
print(email.count("."))
print("sharma" in email, "gmail" in email)
username, domain = email.split("@")
print(username, "|", domain)Output
14
-1
True True
2
True False
priya.sharma42 | example.comHow it works: find() returns the index of the first match, or -1 if there is none. startswith and endswith test the beginning and end. count counts non-overlapping occurrences, and the in operator checks whether one string appears anywhere inside another. Splitting on @ and unpacking the two parts is a quick way to separate the username from the domain.
Reading the result: The @ sits at index 14, the address ends with .com, and the domain is example.com. A missing substring gives -1 from find, so always test the result before using it as an index.
Example 7: Strings are immutable
Question: Can I change one character of a string in place?
code = "FB-1042"
code[0] = "X"Output
Traceback (most recent call last):
File "example.py", line 2, in <module>
code[0] = "X"
~~~~^^^
TypeError: 'str' object does not support item assignmentHow it works: Strings are immutable: once created they cannot be changed. Every method that seems to modify a string actually builds a new one. To change a string you assign the new version back to a name.
Reading the result: Python raises TypeError: 'str' object does not support item assignment. The fix is to build a new string and keep it:
code = "FB-1042"
code = "X" + code[1:]
print(code)Output
XB-1042Here we glued X onto everything after the first character, and re-pointed code at the result.
Example 8: Formatting with f-strings
Question: Print a neat order line: id, customer, amount with commas, and discount as a percentage, aligned in columns.
order = "O02299"
customer = "Aarav Sharma"
amount = 12499.5
discount = 0.125
print(f"{order} | {customer:<14} | Rs. {amount:>10,.2f} | {discount:.1%}")
print(f"{'ID':<8}{'Name':<16}{'Amount':>12}")
print(f"{order:<8}{customer:<16}{amount:>12,.1f}")Output
O02299 | Aarav Sharma | Rs. 12,499.50 | 12.5%
ID Name Amount
O02299 Aarav Sharma 12,499.5How it works: Inside an f-string, {value:format} applies a format specification. <14 left-aligns in 14 characters, >10 right-aligns in 10, , adds thousand separators, .2f shows two decimals, and .1% multiplies by 100 and adds a percent sign. You can also put short expressions inside the braces.
Reading the result: The columns line up, the amount reads 12,499.50, and the discount shows as 12.5%. Learn this mini-language once and every report you print will look professional.
Example 9: Multi-line strings and a mini message template
Question: Produce a customer notification that spans several lines.
name = "Aarav"
eta = 18
message = f"""Hi {name},
Your FreshBasket order is on its way.
Estimated arrival: {eta} minutes.
Thank you!"""
print(message)
print(message.count("\n") + 1, "lines")Output
Hi Aarav,
Your FreshBasket order is on its way.
Estimated arrival: 18 minutes.
Thank you!
4 linesHow it works: Triple quotes keep line breaks exactly as typed, and the f prefix still substitutes variables. count("\n") counts the line breaks, so adding 1 gives the number of lines.
Reading the result: The message prints on four lines, matching the count. Templates like this are the starting point for automated emails and WhatsApp notifications.
Common mistakes
1. Thinking methods change the original. name.upper() does nothing useful on its own line: you must assign it, as in name = name.upper().
2. Off-by-one slicing. "Bengaluru"[0:3] is "Ben", not "Beng". The stop index is excluded.
3. Forgetting that find can return -1.
text = "Mumbai"
position = text.find("x")
print(position, text[position])Output
-1 iThe last character was printed, not an error, because -1 is a valid index. This silent bug is why you should check if position != -1 first.
4. Comparing without normalising. "UPI" == "upi" is False. Lowercase and strip both sides first.
5. Concatenating strings with numbers. "Orders: " + 5 fails. Use an f-string, f"Orders: {5}", or str(5).
Practice questions
Practice 1 (Easy): Clean a name
Convert " rAHUL verma " to the properly cased "Rahul Verma", with single spaces between words and no spaces at the ends.
Show solution
raw = " rAHUL verma "
clean = " ".join(raw.split()).title()
print(repr(clean))Output
'Rahul Verma'raw.split() with no argument splits on any run of whitespace and drops empty pieces, " ".join(...) glues the words back with one space, and .title() fixes the capitals.
Practice 2 (Medium): Build a username
From the email "Vikram.Nair17@Example.COM", create the lowercase username and domain, and print them like vikram.nair17 at example.com.
Show solution
email = "Vikram.Nair17@Example.COM"
username, domain = email.lower().split("@")
print(f"{username} at {domain}")Output
vikram.nair17 at example.comLowercasing first means both parts are normalised, and unpacking the result of split("@") assigns them to two names at once.
Practice 3 (Hard): Parse a feedback line
A feedback record looks like "2025-12-12 | O04344 | rating:3 | Late delivery by almost 50 minutes.". Extract the date, the order id, the rating as an integer, and the comment, without any regular expressions.
Show solution
line = "2025-12-12 | O04344 | rating:3 | Late delivery by almost 50 minutes."
date, order_id, rating_part, comment = [p.strip() for p in line.split("|")]
rating = int(rating_part.split(":")[1])
print(date)
print(order_id)
print(rating, type(rating))
print(comment)Output
2025-12-12
O04344
3 <class 'int'>
Late delivery by almost 50 minutes.We split on the pipe, then used a list comprehension (covered later) to strip each piece; you can read it as "for each part, strip it". The rating part rating:3 splits on the colon, and the second piece "3" is converted to an integer with int().
Key takeaways
- A string is an immutable sequence of characters; indexes start at 0, negative indexes count from the end, and slices exclude the stop index.
- Methods such as
strip,lower,title,replace,splitandjoinreturn new strings, so assign the result. splitturns text into a list,jointurns a list back into text, andin,find,startswithandendswithsearch.- f-strings with format specifications (
:,.2f,:<12,:.1%) produce professional, aligned output. - Always clean before comparing: strip whitespace and normalise case.
- These same methods reappear in pandas as
.str.strip(),.str.lower()and friends, so everything you learn here scales to millions of rows.
Frequently asked questions
What is the difference between a function and a method?
A function stands alone, like len(text). A method belongs to an object and is called with a dot, like text.upper(). Both do jobs, but methods act on the object they are attached to.
Single quotes or double quotes?
No difference in meaning. Pick one style for your project and switch only when the text contains the other kind of quote. Many teams follow the Black code formatter, which prefers double quotes.
How do I include a quote or backslash inside a string?
Use the other kind of quote around it, or escape with a backslash: "She said \"hi\"" and "C:\\data". A raw string, written r"C:\data", treats backslashes literally and is useful for file paths and regular expressions.
Why does "10" + "5" give "105"?
Because + joins strings. To add the numbers, convert them first: int("10") + int("5") is 15.
How can I tell whether a string is all digits or all letters?
Use the test methods isdigit(), isalpha(), isalnum() and isspace(). For example "560034".isdigit() is True. They return False for an empty string.
Are strings case-sensitive when I use in or ==?
Yes. "fresh" in "FreshBasket" is False. Convert both sides with .lower() for case-insensitive checks.
Keep learning Python
One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Ready to practise? Try the Python interview questions