add_csv_column Subroutine

private subroutine add_csv_column(df, data_strings, headers, col_index, has_headers)

Add a column with automatic type detection

Helper subroutine that detects the data type from string data and adds the appropriate typed column to the data frame.

@param[in,out] df The data frame to add the column to @param[in] data_strings Array of string values to convert and add @param[in] headers Optional array of header names @param[in] col_index Index of the column being added @param[in] has_headers Whether the data frame has headers

Note

Type detection priority: integer -> real -> logical -> character

Note

NaN values are preserved for numeric types

Arguments

Type IntentOptional Attributes Name
type(data_frame), intent(inout) :: df
character(len=*), intent(in), dimension(:) :: data_strings
character(len=*), intent(in), optional, dimension(:) :: headers
integer, intent(in) :: col_index
logical, intent(in) :: has_headers

Source Code

    subroutine add_csv_column(df, data_strings, headers, col_index, has_headers)
        type(data_frame), intent(inout) :: df
        character(len=*), dimension(:), intent(in) :: data_strings
        character(len=*), dimension(:), intent(in), optional :: headers
        integer, intent(in) :: col_index
        logical, intent(in) :: has_headers

        integer :: data_type, i, iostat
        real(rk), allocatable :: real_data(:)
        integer(ik), allocatable :: int_data(:)
        logical, allocatable :: logical_data(:)
        real(rk) :: real_val
        integer(ik) :: int_val
        character(len=100) :: trimmed_str

        ! Initialize NaN constant if needed
        call init_nan()

        ! Detect data type from first non-empty, non-NaN value
        data_type = CHARACTER_NUM
        do i = 1, size(data_strings)
            trimmed_str = trim(adjustl(data_strings(i)))

            ! Skip empty strings and common NaN representations
            if (len(trim(trimmed_str)) == 0 .or. &
                trimmed_str == "NaN" .or. trimmed_str == "nan" .or. &
                trimmed_str == "NA" .or. trimmed_str == "na" .or. &
                trimmed_str == "NULL" .or. trimmed_str == "null" .or. &
                trimmed_str == "N/A" .or. trimmed_str == "n/a" .or. &
                trimmed_str == "-" .or. trimmed_str == "") then
                cycle
            end if

            ! Try integer first
            read (data_strings(i), *, iostat=iostat) int_val
            if (iostat == 0) then
                data_type = INTEGER_NUM
                exit
            end if

            ! Try real
            read (data_strings(i), *, iostat=iostat) real_val
            if (iostat == 0) then
                data_type = REAL_NUM
                exit
            end if

            ! Try logical
            if (trimmed_str == "T" .or. trimmed_str == "F" .or. &
                trimmed_str == "true" .or. trimmed_str == "false" .or. &
                trimmed_str == ".true." .or. trimmed_str == ".false.") then
                data_type = LOGICAL_NUM
                exit
            end if

            ! Default to character
            exit
        end do

        ! Convert and add the column
        select case (data_type)
        case (INTEGER_NUM)
            allocate (int_data(size(data_strings)))
            do i = 1, size(data_strings)
                trimmed_str = trim(adjustl(data_strings(i)))

                ! Check for NaN representations
                if (len(trim(trimmed_str)) == 0 .or. &
                    trimmed_str == "NaN" .or. trimmed_str == "nan" .or. &
                    trimmed_str == "NA" .or. trimmed_str == "na" .or. &
                    trimmed_str == "NULL" .or. trimmed_str == "null" .or. &
                    trimmed_str == "N/A" .or. trimmed_str == "n/a" .or. &
                    trimmed_str == "-") then
                    int_data(i) = NaN_ik
                else
                    read (data_strings(i), *, iostat=iostat) int_data(i)
                    if (iostat /= 0) int_data(i) = NaN_ik  ! Use NaN for invalid data
                end if
            end do
            if (has_headers .and. present(headers)) then
                call df_append_integer(df, int_data, headers(col_index))
            else
                call df_append_integer(df, int_data)
            end if
        case (REAL_NUM)
            allocate (real_data(size(data_strings)))
            do i = 1, size(data_strings)
                trimmed_str = trim(adjustl(data_strings(i)))

                ! Check for NaN representations
                if (len(trim(trimmed_str)) == 0 .or. &
                    trimmed_str == "NaN" .or. trimmed_str == "nan" .or. &
                    trimmed_str == "NA" .or. trimmed_str == "na" .or. &
                    trimmed_str == "NULL" .or. trimmed_str == "null" .or. &
                    trimmed_str == "N/A" .or. trimmed_str == "n/a" .or. &
                    trimmed_str == "-") then
                    real_data(i) = NaN_rk
                else
                    read (data_strings(i), *, iostat=iostat) real_data(i)
                    if (iostat /= 0) real_data(i) = NaN_rk  ! Use NaN for invalid data
                end if
            end do
            if (has_headers .and. present(headers)) then
                call df_append_real(df, real_data, headers(col_index))
            else
                call df_append_real(df, real_data)
            end if
        case (LOGICAL_NUM)
            allocate (logical_data(size(data_strings)))
            do i = 1, size(data_strings)
                select case (trim(adjustl(data_strings(i))))
                case ("T", "true", ".true.")
                    logical_data(i) = .true.
                case ("F", "false", ".false.")
                    logical_data(i) = .false.
                case default
                    logical_data(i) = .false.  ! Default for invalid data
                end select
            end do
            if (has_headers .and. present(headers)) then
                call df_append_logical(df, logical_data, headers(col_index))
            else
                call df_append_logical(df, logical_data)
            end if
        case default ! CHARACTER_NUM
            if (has_headers .and. present(headers)) then
                call df_append_character(df, data_strings, headers(col_index))
            else
                call df_append_character(df, data_strings)
            end if
        end select
    end subroutine add_csv_column