Does not work in the same way as the following would not work for matrices:
julia> x = [1 2; 3 4]
2×2 Matrix{Int64}:
1 2
3 4
julia> x[:, 1] = x[:, 1] / 2
ERROR: InexactError: Int64(0.5)
If you are using : on LHS you indicate that you want an in-place operation. This means that target must be able to store the source.
If you use ! on LHS as a selector you indicate that you want to replace the column with new data, overwriting the type of the column:
julia> using DataFrames
julia> df = DataFrame(a=1:2, b=["a", "b"])
2×2 DataFrame
Row │ a b
│ Int64 String
─────┼───────────────
1 │ 1 a
2 │ 2 b
julia> df[:, :a] = df[:, :b] # fails, as it is in place
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int64
julia> df[!, :a] = df[:, :b] # works, as it is a replace operation
2-element Vector{String}:
"a"
"b"
julia> df
2×2 DataFrame
Row │ a b
│ String String
─────┼────────────────
1 │ a a
2 │ b b
Note that writing df[!, :a] is the same as writing df.a.
All this is explained in DataFrames.jl indexing rules | Blog by Bogumił Kamiński.