K-means function in Python
You can vectorize this at various points to apply arithmetic to the whole dataframe rather than row-by-row.
def min_euclidean(df, options):
    &quot;&quot;&quot; Returns the index of the series in iterable options for which df - row has minimum
    Euclidean distance &quot;&quot;&quot;
    return pd.DataFrame(((df - series) ** 2).sum(axis=1, skipna=False) for series in options).idxmin()

def k_means(TE, k):
    iteration = 0
    epoch = 0
    tol = 1
    old_tol = 2
    tols = []
    start_time = time.time()
    mean_cl = [random.uniform(TE.min(), TE.max()) for _ in range(k)]
    o_a = min_euclidean(TE, mean_cl)
    while abs(old_tol - tol) &gt; 0.005:
        old_tol = tol
        epoch += 1
        mean_cl = [TE[o_a == i].mean() for i in range(k)]
        n_a = min_euclidean(TE, mean_cl)
        iteration += len(TE)
        tol = (o_a != n_a).mean()
        tols.append(tol)
        o_a = n_a
    return time.time() - start_time, tols, n_a, iteration

Note that:

wherever possible we work with Pandas series or dataframes instead of lists
I calculate mean_cl as a list of Pandas series instead of a list of lists. This way we avoid iterating through the column or row indices of TE, which is slower. There might be some way to optimize this further by storing mean_cl as a dataframe, but I can't see an obvious way of working with it that way.
there is no need to take the square root for the Euclidean distance - the indices associated with minimal distance will be the same working with the squared values
o_a and n_a are also stored as series. We can then do (o_a!= n_a).mean() to calculate the disparity between them.
The biggest optimization here is probably the min_euclidean function which calculates the square difference for the entire dataframe in an optimized way, rather than iterating through it.

This may be a helpful explanation of vectorization. Working with Pandas series and dataframes in an optimal often involves a slightly different way of thinking than in regular Python.
EDIT: Here is a version using numpy instead of pandas. This fully vectorizes all the operations and is substantially faster again. It uses an optimization from this StackOverflow answer.
def k_means(TE, k):
    epoch = 0
    tol = 1
    old_tol = 2
    tols = []
    start_time = time.time()
    te = np.array(TE)
    rows, columns = te.shape
    te3 = te[:, np.newaxis]  # 3d version of te for calculating euclidean more easily
    k_range = np.arange(k)[:, np.newaxis]
    mean_cl = np.random.uniform(te.min(axis=0), te.max(axis=0), size=(k, columns))
    o_a = np.nanargmin(((te3 - mean_cl) ** 2).sum(axis=2), axis=1)
    while abs(old_tol - tol) &gt; 0.005:
        old_tol = tol
        epoch += 1
        m = o_a == k_range     # masks for each value in range 0 to k-1
        mean_cl = m.dot(te) / m.sum(1, keepdims=True)
        n_a = np.nanargmin(((te3 - mean_cl) ** 2).sum(axis=2), axis=1)
        tol = (o_a != n_a).mean()
        tols.append(tol)
        o_a = n_a
    return time.time() - start_time, tols, n_a, epoch * rows