func NewFromColumns(zero Value, cols ...Column) (Frame, error) {
	maxEndIndex := 0
	columns := make([]Column, len(cols))
	for i, col := range cols {
		columns[i] = col.Copy()
		if i == 0 {
			maxEndIndex = col.Count()
		}
		if maxEndIndex < col.Count() {
			maxEndIndex = col.Count()
		}
	}
	maxEndIndex--
	maxSize := maxEndIndex + 1
	if zero != nil {
		for _, col := range columns {
			rNum := col.Count()
			if rNum < maxSize {  
				for i := 0; i < maxSize-rNum; i++ {
					col.PushBack(zero)
				}
			}
			if rNum > maxSize {
				return nil, fmt.Errorf("something wrong with minimum end index %d (%q has %d rows)", maxEndIndex, col.Header(), rNum)
			}
		}
		rNum := columns[0].Count()
		for _, col := range columns {
			if rNum != col.Count() {
				return nil, fmt.Errorf("%q has %d rows (expected %d rows as %q)", col.Header(), col.Count(), rNum, columns[0].Header())
			}
		}
	}
	combined := New()
	for _, col := range columns {
		if err := combined.AddColumn(col); err != nil {
			return nil, err
		}
	}
	return combined, nil
}
NewFromColumns combines multiple columns into one data frame.
If zero Value is not nil, it makes all columns have the same row number
by inserting zero values where the row number is short compared to the
one with the msot row number. The columns are deep-copied to the Frame.
package dataframe
import (
	"encoding/csv"
	"fmt"
	"sync"
)
type Frame interface {
	Headers() []string
	AddColumn(c Column) error
	Column(header string) (Column, error)
	Columns() []Column
	Count() int
	UpdateHeader(origHeader, newHeader string) error
	MoveColumn(header string, target int) error
	DeleteColumn(header string) bool
	CSV(fpath string) error
	CSVHorizontal(fpath string) error
	Rows() ([]string, [][]string)
	Sort(header string, st SortType, so SortOption) error
}
type frame struct {
	mu       sync.Mutex
	columns  []Column
	headerTo map[string]int
}
func New() Frame {
	return &frame{
		columns:  []Column{},
		headerTo: make(map[string]int),
	}
}
func NewFromRows(header []string, rows [][]string) (Frame, error) {
	if len(rows) < 1 {
		return nil, fmt.Errorf("empty row %q", rows)
	}
	fr := New()
	headerN := len(header)
	if headerN > 0 {  
		cols := make([]Column, headerN)
		for i := range cols {
			cols[i] = NewColumn(header[i])
		}
		for _, row := range rows {
			rowN := len(row)
			if rowN > headerN {
				return nil, fmt.Errorf("header %q is not specified correctly for %q", header, row)
			}
			for j, v := range row {
				cols[j].PushBack(NewStringValue(v))
			}
			if rowN < headerN {  
				for k := rowN; k < headerN; k++ {
					cols[k].PushBack(NewStringValue(""))
				}
			}
		}
		for _, c := range cols {
			if err := fr.AddColumn(c); err != nil {
				return nil, err
			}
		}
		return fr, nil
	}
	header = rows[0]
	headerN = len(header)
	cols := make([]Column, headerN)
	for i := range cols {
		cols[i] = NewColumn(header[i])
	}
	for i, row := range rows {
		if i == 0 {
			continue
		}
		rowN := len(row)
		if rowN > headerN {
			return nil, fmt.Errorf("header %q is not specified correctly for %q", header, row)
		}
		for j, v := range row {
			cols[j].PushBack(NewStringValue(v))
		}
		if rowN < headerN {  
			for k := rowN; k < headerN; k++ {
				cols[k].PushBack(NewStringValue(""))
			}
		}
	}
	for _, c := range cols {
		if err := fr.AddColumn(c); err != nil {
			return nil, err
		}
	}
	return fr, nil
}
func NewFromCSV(header []string, fpath string) (Frame, error) {
	f, err := openToRead(fpath)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	rd := csv.NewReader(f)
	rd.FieldsPerRecord = -1
	rows, err := rd.ReadAll()
	if err != nil {
		return nil, err
	}
	return NewFromRows(header, rows)
}