Calc: How to convert columns into multiple rows

I have a spreadsheet that I am attempting to reformat.

The data provided has one row per paper, in this format:

  • Paper100, Speaker 1 Name, Speaker 1 Email, Speaker 2 Name, Speaker 2 Email, Speaker 3 Name, Speaker 3 Email.

Each paper has between 1 and 8 speakers.

I need to convert it so that instead of each speaker name/email being a different column, they should all be in their own row.

The final result needs to have rows and columns that looks like this:

  • Paper 100, Speaker 1 Name, Speaker 1 Email
  • Paper 100, Speaker 2 Name, Speaker 2 Email
  • Paper 100, Speaker 3 Name, Speaker 3 Email
  • Paper 200, Speaker 1 Name, Speaker 1 Email
  • Paper 200, Speaker 2 Name, Speaker 2 Email

Any advice on how to accomplish that?

If you cut a row (or parts of it) you can insert it as a column: Paste Special | Transpose

1 Like

I’m pretty sure that this guy wasn’t asking for click-drag-n-drop answers. Maybe you’re new to Calc (or spreadsheets) but the idea is to NOT do repetitive things with the mouse and keyboard. I found this page because I was looking for the same thing as the OP and this answer provides literally nothing. No offense intended, but maybe you didn’t know that Calc has built-in functions that do many things like the OPs question.

Since the amount of columns is managable, there is nothing wrong with drag&drop, It gets the job done within a couple of minutes.

Actual data says “Speaker 1”?
E-mails addresses begin with capitals?

I think that (along with Transpose) Find & Replace, and Text to Column could help a lot here.

Here’s a User Defined Function (UDF) that takes the source range and two other parameters and returns an array result with the desired data format.

Really, it’s very straightforward. The happy fact is that you can determine the dimensions of the resulting array upfront just by looking at the fact that you have three kinds of cells in the source range: unoccupied, header, and entry. I’ve put ample documenting comments in the code to explain, I hope. I’m new to this forum (at least as far as posting) so my apologies if I could have formatted this better.

Option Explicit

'Convert denormalized entries to normalized list of entries
	'I.e. Un-nest data fields for key/header field(s) and any following n-tuples of data fields
	'NOTE: Requires all source data to be contiguous from upper left cell of source data range
Function Normalizer(SourceRange As Variant, FieldsPerEntry As Integer, FieldsPerHeader As Integer)
	Dim Result() As Variant
	Dim Dummy As Integer
	Dim OccupiedCellCount As Integer
	Dim HeaderCellCount As Integer
	Dim EntryCellCount As Integer
	Dim EntryCount As Integer
	Dim SourceRowPtr As Integer
	Dim SourceColumnPtr As Integer
	Dim ResultRowPtr As Integer
	Dim ResultColumnPtr As Integer
	Dim Offset As Integer
	
	'Terms:
	'	Header - the lead cell in a denormalized list that is what is in common for each entry
	'	Entry - a set of n fields that follow (possibly in multiples) a header
	'	Field - a single datum that is not a header
	'	Example data:
	'		Paper100, Speaker1Name, Speaker1Email, Speaker2Name, Speaker2Email
	'		Header: Paper100
	'		Fields (2 count): Name, Email
	'		Entries (2 count): Speaker1Name, Speaker1Email --and-- Speaker2Name, Speaker2Email
	' NOTE: LO Calc ranges are managed as 1-based arrays
	
	'Establish it is a two-dimensional array, i.e. a range
	Dummy = -1
	On Error Resume Next
		Dummy = Ubound(SourceRange, 2)
		If Dummy = -1 Then
			Normalizer = CVErr(502) 'Won't be a "real" Err:502, but will show as Error: 502
			Exit Function
		End If
	On Error Goto 0
	
	'Find how many cells within source range are occupied
	OccupiedCellCount = 0
	For SourceRowPtr = LBound(SourceRange) To UBound(SourceRange)
		For SourceColumnPtr = LBound(SourceRange, 2) To UBound(SourceRange, 2)
			If Not (SourceRange(SourceRowPtr, SourceColumnPtr) = "") Then OccupiedCellCount = OccupiedCellCount + 1
		Next SourceColumnPtr
	Next SourceRowPtr
	
	'Find how many source range cells are headers
	'	Which is just FieldsPerHeader per source range row
	HeaderCellCount = (UBound(SourceRange) - LBound(SourceRange) + 1) * FieldsPerHeader
	
	'Find how many source range cells are part of entries
	'	If occupied, must be header cell or entry cell
	EntryCellCount = OccupiedCellCount - HeaderCellCount
	
	'Find how many entries are in the source range
	'	This will determine the number of rows of the result array
	EntryCount = EntryCellCount \ FieldsPerEntry
	
	'Create the result array
	ReDim Result(1 To EntryCount, 1 To FieldsPerHeader + FieldsPerEntry)
	'Xray REsult
	'Step through the source range and parcel out to result array
	ResultRowPtr = 1
	For SourceRowPtr = LBound(SourceRange) To UBound(SourceRange)
		SourceColumnPtr = FieldsPerHeader + 1
		Do While SourceColumnPtr <= UBound(SourceRange, 2)
			If (Trim(SourceRange(SourceRowPtr, SourceColumnPtr)) <> "") And Not (IsEmpty(SourceRange(SourceRowPtr, SourceColumnPtr)))  Then
				For Offset = 1 to FieldsPerHeader
					Result(ResultRowPtr, Offset) = SourceRange(SourceRowPtr, Offset)
				Next Offset
				For Offset = 0 to FieldsPerEntry - 1
					ResultColumnPtr = FieldsPerHeader + (Offset + 1) 'Advance 1, 2 etc., past header cell(s)
					Result(ResultRowPtr, ResultColumnPtr) = SourceRange(SourceRowPtr, SourceColumnPtr + Offset)
				Next Offset
				ResultRowPtr = ResultRowPtr + 1 'WIll advance out of bounds at end, but won't be used then
			End If
			SourceColumnPtr = SourceColumnPtr + FieldsPerEntry
		Loop
	Next SourceRowPtr
	
	Normalizer = Result
End Function	

Speaker100.ods (14.8 KB)

1 Like