You've omitted some things here that make it difficult to see the reason.
First, let's start by using the built-in dir function to see what we actually get when import pandas:
import pandas as pd
print(dir(pd))
#['BooleanDtype', 'Categorical', 'CategoricalDtype', 'CategoricalIndex',
# 'DataFrame', 'DateOffset', 'DatetimeIndex', 'DatetimeTZDtype', 'ExcelFile',
# 'ExcelWriter', 'Flags', 'Float32Dtype', 'Float64Dtype', 'Float64Index',
# 'Grouper', 'HDFStore', 'Index', 'IndexSlice', 'Int16Dtype', 'Int32Dtype',
# 'Int64Dtype', 'Int64Index', 'Int8Dtype', 'Interval', 'IntervalDtype',
# 'IntervalIndex', 'MultiIndex', 'NA', 'NaT', 'NamedAgg', 'Period', 'PeriodDtype',
# 'PeriodIndex', 'RangeIndex', 'Series', 'SparseDtype', 'StringDtype', 'Timedelta',
# 'TimedeltaIndex', 'Timestamp', 'UInt16Dtype', 'UInt32Dtype', 'UInt64Dtype',
# 'UInt64Index', 'UInt8Dtype', '__builtins__', '__cached__', '__doc__',
# '__docformat__', '__file__', '__getattr__', '__git_version__', '__loader__',
# '__name__', '__package__', '__path__', '__spec__', '__version__', '_config',
# '_hashtable', '_is_numpy_dev', '_lib', '_libs', '_np_version_under1p17',
# '_np_version_under1p18', '_testing', '_tslib', '_typing', '_version', 'api',
# 'array', 'arrays', 'bdate_range', 'compat', 'concat', 'core', 'crosstab', 'cut',
# 'date_range', 'describe_option', 'errors', 'eval', 'factorize', 'get_dummies',
# 'get_option', 'infer_freq', 'interval_range', 'io', 'isna', 'isnull',
# 'json_normalize', 'lreshape', 'melt', 'merge', 'merge_asof', 'merge_ordered',
# 'notna', 'notnull', 'offsets', 'option_context', 'options', 'pandas',
# 'period_range', 'pivot', 'pivot_table', 'plotting', 'qcut', 'read_clipboard',
# 'read_csv', 'read_excel', 'read_feather', 'read_fwf', 'read_gbq', 'read_hdf',
# 'read_html', 'read_json', 'read_orc', 'read_parquet', 'read_pickle', 'read_sas',
# 'read_spss', 'read_sql', 'read_sql_query', 'read_sql_table', 'read_stata',
# 'read_table', 'reset_option', 'set_eng_float_format', 'set_option',
# 'show_versions', 'test', 'testing', 'timedelta_range', 'to_datetime',
# 'to_numeric', 'to_pickle', 'to_timedelta', 'tseries', 'unique', 'util',
# 'value_counts', 'wide_to_long']
I want to point out two specific things that were imported DataFrame and read_csv, which you mentioned in your question.
Now, if you try to use either of these things directly (i.e without the pd prefix), the compiler will throw a NameError:
df = DataFrame(data=[1,2,3]) # NameError: name 'DataFrame' is not defined
x = read_csv("my_file.csv") # NameError: name 'read_csv' is not defined
When you directly do import pandas as pd, it keeps all the newly imported pandas functions and objects in a sort of box, which you have to refer to explicitly or else the compiler will not know what function you're referring to. This is done intentionally, since there may be namespace clashes between two modules. Take the where function for example - both numpy and pandas have a where function - if you just call where(), how will the compiler know which one you're referring to?
This can be avoided by using the * operator, like so:
import numpy as np
from pandas import *
x = np.array([1,2,3,4,5])
y = np.where((x%2==0), x, -1) # you MUST use np prefix here to refer to the numpy
# "box" of imported functions.
z = x.where(x > 1, -2) # no prefix needed, since * operator was used
Note that you can only use * on one module that you import - you must use a regular import for the rest.
The last thing I want to address is the fact that no prefix is needed when you run df.to_csv(...). This is because df is not something you imported from pandas directly - instead, you initialized df beforehand like this:
import pandas as pd
df = pd.DataFrame(data=[1,2,3])
df.to_csv(...)
No prefix is needed for your function call here because df is something you created! It is a variable whose value has been initialized to a pd.DataFrame object, which can access its internal class method to_csv when you call it. It's almost like an alias for the DataFrame object.
To illustrate this point, you could even do something silly like this:
import pandas as pd
alias_func = pd.read_csv
z = alias_func("my_file.csv")
In short, you only need to use the module prefix when you're actually calling a function imported from a module that was imported without the * operator. You do not need to put the prefix when using variables/objects you initialize to pandas functions or objects, because you have already told the compiler which module's "box" you're referring to, and your new variable just acts as an alias.