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
Type | Intent | Optional | 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 |
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